mobilohas / object

object 책 읽기 스터디
3 stars 1 forks source link

[Chapter05] 이미 데이터 중심적으로 구현된 프로그램을 효율적으로 개선하려면 어떻게 해야할까요? #18

Closed YJGwon closed 2 months ago

YJGwon commented 3 months ago

희망편은 처음 설계부터 객체중심적으로 해나가는 것일 테지만, 실제로 실무에서는 이미 데이터 중심적으로 만들어진 코드를 다루게 될 일도 많은 것 같아요. 이미 데이터 중심적으로 설계된 코드는 변경이 어려워서 리팩토링 역시 큰 비용을 수반하게 되는데요, 이럴 경우에 어떤 시점에 리팩토링을 해야 비용 측면에서 실용적인 리팩토링을 할 수 있을까요? 또, 어떻게 리팩토링해야 단계적으로 개선해나갈 수 있을까요?

pythonstrup commented 3 months ago

단계적 개선

  1. 가장 이상적인 구조를 먼저 생각해 본다. 설계를 다시 해본다. (사실 저는 1번은 잘 안하는 거 같아요...ㅋㅋㅋㅋ)
    • 현재 객체의 행동을 전부 정리한다.
    • 객체의 행동에 따라 객체의 협력을 재정의해보거나 새로운 객체를 추가해본다.
  2. 캡슐화할 수 있는 기능들을 먼저 캡슐화해본다.
  3. 객체를 추가해 책임을 나눠본다.
  4. 객체의 역할이 보인다면 추상화(인터페이스, 추상클래스)를 통해 역할을 정의하고 구현체를 나눠본다.

추상화가 가장 나중인 이유는?

pythonstrup commented 3 months ago

책임 분리

public class Screening {

  private Movie movie;
  private int sequence;
  private LocalDateTime whenScreened;

  public Screening(final Movie movie, final int sequence, final LocalDateTime whenScreened) {
    this.movie = movie;
    this.sequence = sequence;
    this.whenScreened = whenScreened;
  }

  public Money calculateFee(final int audienceCount) {
    return movie.calculateFee(whenScreened, sequence).times(audienceCount);
  }
}
public class Movie {

  private String title;
  private Duration runningTime;
  private Money fee;
  private DiscountPolicy discountPolicy;

  public Movie(final String title, final Duration runningTime, final Money fee, final DiscountPolicy discountPolicy) {
    this.title = title;
    this.runningTime = runningTime;
    this.fee = fee;
    this.discountPolicy = discountPolicy;
  }

  public Money calculateFee(LocalDateTime whenScreened, int sequence) {
    return fee.minus(discountPolicy.calculateDiscountAmount(whenScreened, sequence));
  }
}
public class DiscountPolicy {

  private DiscountType discountType;
  private Money discountAmount;
  private double discountPercent;
  private List<DiscountCondition> discountConditions;

  public Money calculateDiscountAmount(LocalDateTime whenScreened, int sequence, Money movieFee) {
    if (!isDiscountable(whenScreened, sequence)) {
      return Money.ZERO;
    }
    return switch (discountType) {
      case AMOUNT_DISCOUNT -> discountAmount;
      case PERCENT_DISCOUNT -> movieFee.times(discountPercent);
      case NONE_DISCOUNT -> Money.ZERO;
    };
  }

  private boolean isDiscountable(LocalDateTime whenScreened, int sequence) {
    for (DiscountCondition condition : discountConditions) {
      if (condition.getType().equals(DiscountConditionType.PERIOD)) {
        if (condition.isDiscountable(whenScreened.getDayOfWeek(), whenScreened.toLocalTime())) {
          return true;
        }
      } else {
        if (condition.isDiscountable(sequence)) {
          return true;
        }
      }
    }
    return false;
  }
}
pythonstrup commented 3 months ago

역할 분리

public abstract class DiscountPolicy {

  private List<DiscountCondition> discountConditions;

  public DiscountPolicy(final List<DiscountCondition> discountConditions) {
    this.discountConditions = discountConditions;
  }

  public Money calculateDiscountAmount(LocalDateTime whenScreened, int sequence, Money movieFee) {
    if(isDiscountable(whenScreened, sequence)) {
      return calculateDiscountPolicyAmount(whenScreened, sequence, movieFee);
    }
    return Money.ZERO;
  }

  protected boolean isDiscountable(LocalDateTime whenScreened, int sequence) {
    for (DiscountCondition condition : discountConditions) {
      if (condition.getType().equals(DiscountConditionType.PERIOD)) {
        if (condition.isDiscountable(whenScreened.getDayOfWeek(), whenScreened.toLocalTime())) {
          return true;
        }
      } else {
        if (condition.isDiscountable(sequence)) {
          return true;
        }
      }
    }
    return false;
  }

  abstract protected Money calculateDiscountPolicyAmount(
                       LocalDateTime whenScreened, int sequence, Money movieFee);
}
public class AmountDiscountPolicy extends DiscountPolicy {

  private Money discountAmount;

  public AmountDiscountPolicy(
      final Money discountAmount,
      final List<DiscountCondition> discountConditions) {
    super(discountConditions);
    this.discountAmount = discountAmount;
  }

  @Override
  public Money calculateDiscountPolicyAmount(
      final LocalDateTime whenScreened, final int sequence, final Money movieFee) {
    return discountAmount;
  }
}
public class PercentDiscountPolicy extends DiscountPolicy {

  private double discountPercent;

  public PercentDiscountPolicy(
      final double discountPercent,
      final List<DiscountCondition> discountConditions) {
    super(discountConditions);
    this.discountPercent = discountPercent;
  }

  @Override
  public Money calculateDiscountPolicyAmount(
      final LocalDateTime whenScreened, final int sequence, final Money movieFee) {
    return movieFee.times(discountPercent);
  }
}
public class NoneDiscountPolicy extends DiscountPolicy {

  public NoneDiscountPolicy(final List<DiscountCondition> discountConditions) {
    super(discountConditions);
  }

  @Override
  public Money calculateDiscountPolicyAmount(
      final LocalDateTime whenScreened, final int sequence, final Money movieFee) {
    return Money.ZERO;
  }
}
pythonstrup commented 3 months ago
JisuPark-dev commented 3 months ago

밸이 위에서 단계적 개선을 4가지 단계로 말씀해주셨는데요! 1번이 안된 상태에서 캡슐화하면 적절하지 못한 책임이 캡슐화될 것 같아요. (왠지 모비닥에는 그런 경우가 많은 것 같아요...!) 그래서 모비닥 만약에 리팩토링하게 된다면, 설계부터 그려본 후에 캡슐화를 시도해볼 것 같습니다!