www.notion.so/4-abaa24321532499abe2ccf1ac0821531

 

4. 책임주도 설계의 대안

책임주도설계는 익숙해지기위해서 부단한 노력과 시간이 필요하다.

www.notion.so

  • 책임주도설계는 익숙해지기위해서 부단한 노력과 시간이 필요하다.
  • ( 매 우 어 렵 다 )
  • 이럴때는 최대한 빠르게 목적한 기능을 수행하는 코드를 작성하는 것이다.
  • 일단 코드상에 명확하게 드러나는 책임들을 보고 협력과 책임에 관해 고민하면서 책임들을 올바른 위치로 옮기는 것이다.
  • 주로 객체지향 설계에 대한 경험이 부족한 개발자들과 페어 프로그래밍을 할때나 설계의 실마리가 풀리지 않을때 이런 방법을 사용하는데 생각보다 훌륭한 설계를 얻게 되는 경우가 종종 있다.
  • 주의할 점은 코드를 수정 한 후에 겉으로 드러나는 동작이 바뀌어서는 안된다는 것이다.
  • 캡슐화를 향상 시키고 응집도를 높이고 결합도를 낮춰야 하지만 동작은 그대로 유지해야 한다.
  • 이처럼 이해하기 쉽고 수정하기 쉬운 소프트웨어로 개산하기위해 겉으로 보이는 동작은 바꾸지 않은채 내부 구조를 변경하는 것을 리팩토링 이라고 부른다.

메서드 응집도

  • 데이터 중심으로 설계된 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); } }

  • 단점
    1. 어떤일을 수행하는지 한눈에 파악하기 어렵기 때문에 코드를 전체적으로 이해하는데 너무 많은 시간이 걸린다.
    2. 하나의 메서드 안에서 너무 많은 작업을 처리하기 때문에 변경이 필요할 때 수정해야 할 부분을 찾기 어렵다.
    3. 메소드 내부의 일부 로직만 수정하더라도 메소드의 나머지 부분에서 버그가 발생할 확률이 높다.
    4. 로직의 일부만 재사용하는 것이 불가능하다.
    5. 코드를 재사용하는 유일한 방법은 원하는 코드를 복사해서 붙여넣는것 뿐이므로 코드 중복을 초래하기 쉽다.
  • 한마디로 말해서 응집도가 낮아서 이해하기도 어렵고 재사용하기도 어려우며 변경하기도 어렵다 이러한 메소드를 몬스터 메소드라고 부른다.
  • 우선 메소드를 작게 분리해서 메소드의 응집도를 높여라.
  • 응집도가 높은 메소드는 변경되는 이유가 단 하나여야 한다.
  • 클래스가 작고 목적이 명확한 메소드들로 구성돼 있다면 변경을 처리하기 위해 어떤 메소드를 수정해야 하는지를 쉽게 판단할 수 있다.
  • 메소드의 크기가 작고 목적이 분명하기 떄문에 재사용 하기도 쉽다.
  • 위와 같은 이유로 메소드를 짧게 만들고 이해하기 쉬운 이름으로된 메소드로 만들자.

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 패턴 을 사용하면 아까와 유사한 객체지향설계로 설계된 구조가 나온다.

+ Recent posts