www.notion.so/4-abaa24321532499abe2ccf1ac0821531
- 책임주도설계는 익숙해지기위해서 부단한 노력과 시간이 필요하다.
- ( 매 우 어 렵 다 )
- 이럴때는 최대한 빠르게 목적한 기능을 수행하는 코드를 작성하는 것이다.
- 일단 코드상에 명확하게 드러나는 책임들을 보고 협력과 책임에 관해 고민하면서 책임들을 올바른 위치로 옮기는 것이다.
- 주로 객체지향 설계에 대한 경험이 부족한 개발자들과 페어 프로그래밍을 할때나 설계의 실마리가 풀리지 않을때 이런 방법을 사용하는데 생각보다 훌륭한 설계를 얻게 되는 경우가 종종 있다.
- 주의할 점은 코드를 수정 한 후에 겉으로 드러나는 동작이 바뀌어서는 안된다는 것이다.
- 캡슐화를 향상 시키고 응집도를 높이고 결합도를 낮춰야 하지만 동작은 그대로 유지해야 한다.
- 이처럼 이해하기 쉽고 수정하기 쉬운 소프트웨어로 개산하기위해 겉으로 보이는 동작은 바꾸지 않은채 내부 구조를 변경하는 것을 리팩토링 이라고 부른다.
메서드 응집도
- 데이터 중심으로 설계된 ReservationAgency에 포함된 로직들을 적절한 객체의 책임으로 분배하면 책임주도설계와 거의 유사한 결과를 얻을 수 있다.
public class ReservationAgency { public Reservation reserve(Screening screening, Customer customer, int audienceCount) { Movie movie = screening.getMovie(); boolean discountable = false; for(DiscountCondition condition : movie.getDiscountConditions()) { if (condition.getType() == DiscountConditionType.PERIOD) { discountable = screening.getWhenScreened().getDayOfWeek().equals(condition.getDayOfWeek()) && condition.getStartTime().compareTo(screening.getWhenScreened().toLocalTime()) <= 0 && condition.getEndTime().compareTo(screening.getWhenScreened().toLocalTime()) >= 0; } else { discountable = condition.getSequence() == screening.getSequence(); } if (discountable) { break; } } Money fee; if (discountable) { Money discountAmount = Money.ZERO; switch(movie.getMovieType()) { case AMOUNT_DISCOUNT: discountAmount = movie.getDiscountAmount(); break; case PERCENT_DISCOUNT: discountAmount = movie.getFee().times(movie.getDiscountPercent()); break; case NONE_DISCOUNT: discountAmount = Money.ZERO; break; } fee = movie.getFee().minus(discountAmount).times(audienceCount); } else { fee = movie.getFee().times(audienceCount); } return new Reservation(customer, screening, fee, audienceCount); } }
- 단점
- 어떤일을 수행하는지 한눈에 파악하기 어렵기 때문에 코드를 전체적으로 이해하는데 너무 많은 시간이 걸린다.
- 하나의 메서드 안에서 너무 많은 작업을 처리하기 때문에 변경이 필요할 때 수정해야 할 부분을 찾기 어렵다.
- 메소드 내부의 일부 로직만 수정하더라도 메소드의 나머지 부분에서 버그가 발생할 확률이 높다.
- 로직의 일부만 재사용하는 것이 불가능하다.
- 코드를 재사용하는 유일한 방법은 원하는 코드를 복사해서 붙여넣는것 뿐이므로 코드 중복을 초래하기 쉽다.
- 한마디로 말해서 응집도가 낮아서 이해하기도 어렵고 재사용하기도 어려우며 변경하기도 어렵다 이러한 메소드를 몬스터 메소드라고 부른다.
- 우선 메소드를 작게 분리해서 메소드의 응집도를 높여라.
- 응집도가 높은 메소드는 변경되는 이유가 단 하나여야 한다.
- 클래스가 작고 목적이 명확한 메소드들로 구성돼 있다면 변경을 처리하기 위해 어떤 메소드를 수정해야 하는지를 쉽게 판단할 수 있다.
- 메소드의 크기가 작고 목적이 분명하기 떄문에 재사용 하기도 쉽다.
- 위와 같은 이유로 메소드를 짧게 만들고 이해하기 쉬운 이름으로된 메소드로 만들자.
public class ReservationAgency { public Reservation reserve(Screening screening, Customer customer, int audienceCount) { boolean discountable = checkDiscountable(screening); Money fee = calculateFee(screening, discountable, audienceCount); return createReservation(screening, customer, audienceCount, fee); } private boolean checkDiscountable(Screening screening) { return screening.getMovie().getDiscountConditions().stream() .anyMatch(condition -> condition.isDiscountable(screening)); } private Money calculateFee(Screening screening, boolean discountable, int audienceCount) { if (discountable) { return screening.getMovie().getFee() .minus(calculateDiscountedFee(screening.getMovie())) .times(audienceCount); } return screening.getMovie().getFee(); } private Money calculateDiscountedFee(Movie movie) { switch(movie.getMovieType()) { case AMOUNT_DISCOUNT: return calculateAmountDiscountedFee(movie); case PERCENT_DISCOUNT: return calculatePercentDiscountedFee(movie); case NONE_DISCOUNT: return calculateNoneDiscountedFee(movie); } throw new IllegalArgumentException(); } private Money calculateAmountDiscountedFee(Movie movie) { return movie.getDiscountAmount(); } private Money calculatePercentDiscountedFee(Movie movie) { return movie.getFee().times(movie.getDiscountPercent()); } private Money calculateNoneDiscountedFee(Movie movie) { return movie.getFee(); } private Reservation createReservation(Screening screening, Customer customer, int audienceCount, Money fee) { return new Reservation(customer, screening, fee, audienceCount); } }
- 전체적인 흐름을 이해하기가 쉬워진다.
- 각 메서드는 단 하나의 이유에 의해서만 변경된다.
- 메서드들의 응집도 자체는 높아졌지만 클래스의 응집도는 여전히 낮다.
- 클래스의 응집도를 높이기 위해서는 메소드들의 책임을 적절한 위치로 이동시켜야 한다.
객체를 자율적으로 만들자.
- 객체를 자율적으로 만든다는것은 자신이 소유하고 있는 데이터를 자기 스스로 처리하도록 만드는것이 자율적인 객체를 만드는 지름길이다.
- 따라서 메소드가 사용하는 데이터를 저장하고 있는 클래스로 메소드를 이동시키면 된다.
- 일반적으로 메소드를 다른클래스로 이동시킬때는 인자에 정의된 클래스 중 하나로 이동하는 경우가 일반적이다.
- 이렇게 책임을 옮긴후에 Polymorphism 패턴과 Protected Variations 패턴 을 사용하면 아까와 유사한 객체지향설계로 설계된 구조가 나온다.
'book > 오브젝트' 카테고리의 다른 글
5장 - 3. 구현을 통한 검증 (0) | 2021.05.10 |
---|---|
5장 - 2. 책임 할당을 위한 GRASP 패턴 (0) | 2021.05.09 |
5장 - 1 책임 할당하기 (0) | 2021.05.08 |
4장 설계 품질과 트레이드오프 (0) | 2021.05.05 |
오브젝트 3장 - 역할, 책임, 협력 (0) | 2021.04.30 |