본문 바로가기
Programming/객체지향프로그래밍입문

# 기능과 책임 분리

by JKROH 2023. 1. 28.
반응형

Main Point : 큰 기능과 지나친 책임은 코드 변경을 어렵게 한다.


- 기능 분해

  • 하나의 큰 기능은 여러 개의 하위 기능으로 분해가 가능하다.
  • 아래의 예시를 통해 살펴보자.

더보기

 해당 다이어그램은 '상품 구입'이라는 기능을 단계별로 분해한 것이다.

상품 구입은 구입할 상품을 확인하는 기능과 실제로 해당 상품을 결제하는 기능으로 나뉠 수 있다. 구입할 상품을 확인하는 기능은 다시 해당 상품의 재고를 확인하는 기능과 재고가 없을 시 오류를 보내는 기능으로 나뉠 수 있다. 마찬가지로 상품을 결제하는 기능도 결제 방식을 확인하는 기능과 결제를 진행하는 기능으로 나뉠 수 있다.

  예시를 위해 단순한 분해만을 진행한 결과이며 위의 기능은 더욱 분리될 수 있다. 

 

- 기능을 누가 제공할 것인가?

  • 분리한 하위 기능을 누가 제공할 것인지를 결정하는 것이 객체지향 설계의 기본 과정이다.
  • 기능을 분리하고 각 객체에 분리할 기능을 제공할 책임을 배분해야 한다.
  • 위의 예시에서 상품 구입은 Purchase, 상품 확인은 MerchandiseRepository, 상품 결제는 Payment 정도의 클래스를 만들어 기능과 책임을 배분할 수 있을 것이다.
  • 객체지향 설계는 하위 기능을 알맞은 객체에 분배하는 방식으로 진행된다.

 

- 하위 기능 사용

  • 하위 기능을 사용하여 위의 예시를 코드로 구현한 예시는 아래와 같을 수 있다.
public class Purchase {
    
    MerchandiseRepository merchandiseRepository = new MerchandiseRepository();
    Payment payment = new Payment();
    
    public void buyMerchandise(String merchandiseName) {
         Merchandise merchandise = merchandiseRepository.getMerchandise(merchandiseName);
         
             ... //구매와 관련한 코드
             
         payment.pay(merchandise);    
    }

 

- 큰 클래스, 큰 메소드

  • 한 클래스나 메소드가 크다면, 다시 말해 소스 코드가 길다면 절차지향과 비슷한 문제가 발생한다.
  • 클래스나 메소드가 크다면 여러 기능이 한 클래스나 메소드에 몰리게 되면서 많은 필드와 변수를 공유할 가능성이 높아진다.
  • 즉, 데이터인 필드와 변수를 서로 다른 코드에서 읽고 변경하기 때문에 점점 코드를 수정하기 어려운 구조가 된다.
  • 따라서 클래스나 메소드가 크다면 책임에 따라 알맞게 분리해서 하위 기능을 분배해야 한다.

 

- 책임 분배 / 분리 방법

  • 패턴 적용
  • 계산 기능 분리
  • 외부 연동 분리
  • 조건별 분기는 추상화

 

- 패턴 적용

  • 전형적인 역할 분리 패턴은 다음과 같다.
    • 간단한 웹 = 컨트롤러, 서비스, DAO
    • 복잡한 도메인 = 엔티티, 밸류, 레포지토리, 도메인 서비스
    • AOP = Aspect(공통 기능)
    • GoF = 팩토리, 빌더, 전략, 템플릿 메소드, 프록시 및 데코레이터 등

 

- 계산 기능 분리

  • 아래의 코드는 시험 점수에 따라 등급을 다르게 나누는 코드이다. 해당 예시에서 계산 기능을 분리해보자.
Test test = new Test();

int score = test.getScore();

String gradeIndex;

if (score >= 90) {
    gradeIndex = "A";
}
else if (score<90 && score>=80) {
    gradeIndex = "B";
}

    ...
    
    
Grade grade = new Grade(gradeIndex);
더보기

 예시가 잘 들린지는 모르겠지만 계산 기능을 분리한다는 점에서만 생각해보자. 해당 코드에서 score에 따라 gradeIndex의 값을 바꾸는 코드는 등급을 계산하는 코드이다. 이 부분만 따로 분리해보자.

Test test = new Test();

int score = test.getScore();

GradeCalculator gradeCal = new GradeCalculator(score);

Grade grade = gradeCal.calculate();

////////////////////////////////////////////////////////

public class GradeCalculaotr {
    score ... 
    
    public Grade calculate(){
         String gradeIndex;
         
         if (score >= 90) {
             gradeIndex = "A";
         }
         else if (score<90 && score>=80) {
             gradeIndex = "B";
         }

             ...
    
    
         return new Grade(gradeIndex);
     }

 

- 외부 연동 분해

  • 네트워크, 파일 등 연동 처리 코드는 외부 연동을 수행하는 별도의 클래스로 분리한다.

 

- 조건별 분기의 추상화

  • 연속적인 if-else는 추상화를 고민한다.
  • 각 if에서 하는 일이 비슷하다면 공통적인 부분으로 추상화한다.

 

- 의도가 잘 드러나는 이름 사용

  • 기능을 분리할 때는 의도나 의미가 잘 드러나는 이름을 사용해야한다.
  • 기능의 이름에 의도가 정확하게 드러나지 않는다면, 해당 기능이 무엇인지 알아보기 위해 소스코드를 직접 봐야한다. 이는 생산성을 크게 저하시킨다.
  • 기능을 분리할 때, 해당 기능을 명확하게 대표할 수 있는 이름이 없다면, 또는 해당 기능의 이름이 다른 더 큰 기능의 이름의 의미에 포함되어 있다면 기능을 잘못 분해한 것이 아닌지를 생각해보아야 한다.
  • 예를 들어, 유저별 추천 데이터를 읽어오는 기능을 분리한다고 하면 UserDataService보다 RecommendService가 더 좋은 이름일 것이다.

 

- 역할 분리와 테스트

  • 역할 분리가 잘 되면 테스트가 용이해진다.
  • 위의 점수 코드를 예시로 들었을 때, 기존의 코드에서는 등급을 정확하게 계산하는지를 테스트하려면 Test 객체를 만드는 과정부터 실행해야 계산 코드를 테스트해볼 수 있다.
  • 그러나 계산 기능을 분리함으로써 다른 데이터와 관계 없이 점수 데이터만으로 등급 계산 로직을 테스트 할 수 있다.

 

 

반응형

'Programming > 객체지향프로그래밍입문' 카테고리의 다른 글

# 의존과 DI  (0) 2023.01.29
# 상속보다는 조립  (0) 2023.01.27
# 다형성과 추상화  (0) 2023.01.19
# 캡슐화  (0) 2023.01.16
# 객체  (0) 2023.01.16

댓글