Programming/객체지향프로그래밍입문
# 기능과 책임 분리
JKROH
2023. 1. 28. 19:20
반응형
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 객체를 만드는 과정부터 실행해야 계산 코드를 테스트해볼 수 있다.
- 그러나 계산 기능을 분리함으로써 다른 데이터와 관계 없이 점수 데이터만으로 등급 계산 로직을 테스트 할 수 있다.
반응형