= NightlyDiscountPhone.LATE_NIGHT_HOUR ) { nightlyFee = nightlyFee.plus( this.nightlyAmount.times( eachCall.getDurationInSeconds() / super.getSeconds() ) ); } } return result.minus(nightlyFee); } } "> = NightlyDiscountPhone.LATE_NIGHT_HOUR ) { nightlyFee = nightlyFee.plus( this.nightlyAmount.times( eachCall.getDurationInSeconds() / super.getSeconds() ) ); } } return result.minus(nightlyFee); } } "> = NightlyDiscountPhone.LATE_NIGHT_HOUR ) { nightlyFee = nightlyFee.plus( this.nightlyAmount.times( eachCall.getDurationInSeconds() / super.getSeconds() ) ); } } return result.minus(nightlyFee); } } ">
import { Money } from "./money";
import { Phone } from "./phone";

export class NightlyDiscountPhone extends Phone {
  private static readonlyLATE_NIGHT_HOUR= 22;

  constructor(
    regularAmount: Money,
    seconds: number,
    taxRate: number,
    private readonly nightlyAmount: Money
  ) {
    super(seconds, taxRate, regularAmount);
    this.nightlyAmount = nightlyAmount;
  }

  public calculateFee(): Money {
    let result = super.calculateFee();
    let nightlyFee = Money.ZERO;

    for (const eachCall of super.getCalls()) {
      if (
        eachCall.getFrom().getHours() >= NightlyDiscountPhone.LATE_NIGHT_HOUR
) {
        nightlyFee = nightlyFee.plus(
          this.nightlyAmount.times(
            eachCall.getDurationInSeconds() / super.getSeconds()
          )
        );
      }
    }
    return result.minus(nightlyFee);
  }
}

Getting started

전통적인 패러다임에서 코드를 재사용 하는 방법은 코드를 복사한 후 수정하는 것이다. 객체지향은 조금 다른 방법을 취한다. 객체지향에서는 코드를 재사용하기 위해 새로운 코드를 추가한다.

Inheritance and duplication

중복 코드는 우리를 주저하게 만들 뿐 아니라 동료들을 의심하게 만든다. 이것만으로도 중복 코드를 제거해야 할 충분한 이유가 되고도 남겠지만, 결정적인 이유는 따로 있다.

중복 코드는 변경을 방해한다. 이것이 중복 코드를 제거해야 하는 가장 큰 이유이다. 프로그램의 본질은 비즈니스와 관련된 지식을 코드로 변환하는 것이다. 안타깝게도 이 지식은 항상 변한다. 그에 맞춰 지식을 표현하는 코드 역시 변경해야 한다. 그 이유가 무엇이건, 일단 새로운 코드를 추가하고 나면 언젠가는 변경될 것이라고 생각하는 게 현명하다.

중복 코드가 가지는 가장 큰 문제는 코드를 수정하는 데 필요한 노력을 몇 배로 증가시킨다는 것이. 우선 어떤 코드가 중복인지를 찾아야 한다. 일단 중복 코드의 묶음을 찾았다면 찾아낸 모든 코드를 일관되게 수정해야 한다. 모든 중복 코드를 개별적으로 테스트 해서 동일한 결과를 내 놓는지 확인해야만 한다. 중복 코드는 수정과 테스트에 드는 비용을 증가시킬 뿐만 아니라 시스템과 여러분을 공황 상태로 몰아넣을 수도 있다.

DRY, Don’t Repeat Yourself

모든 지식은 시스템 내에서 단일하고, 애매하지 않고, 정말로 믿을 만한 표현 양식을 가져야 한다.

DRY 는 Once and Only Once, Single Point Control 이라고도 부르기도 한다. 원칙의 이름이 무엇이던 간에 핵심은 코드 안에 중복이 존재해서는 안 된다는 것이다.

Example of duplication in codes

중복 코드의 문제점을 이해하기 위해 한 달에 한 번 가입자별로 전화 요금을 계산하는 간단한 애플리케이션을 개발해 보자. 전화 요금을 계산하는 규칙은 간단한데, 통화 시간을 단위 시간당 요금으로 나눠 주면 된다

10초당 5원의 통화료를 부과하는 요금제에 가입되어 있는 사용자가 100초 동안 통화를 했다면 요금으로 $100 / 10 * 5$, 즉 50원이 부과된다.

export class Call {
  constructor(private readonly from: Date, private readonly to: Date) {}

  public getDurationInSeconds(): number {
    return this.to.getSeconds() - this.from.getSeconds();
  }

  public getFrom(): Date {
    return this.from;
  }
}
export class Phone {
  private readonly callList: Call[] = [];

  constructor(
    private readonly amount: Money,
    private readonly seconds: number
  ) {}

  public call(call: Call): void {
    this.callList.push(call);
  }

  public getCalls(): Call[] {
    return this.callList;
  }

  public getAmount() {
    return this.amount;
  }

  public getSeconds() {
    return this.seconds;
  }

  public calculateFee(): Money {
    const result = Money.ZERO;
    this.callList.forEach((eachCall) => {
      result.plus(
        this.amount.times(eachCall.getDurationInSeconds() / this.seconds)
      );
    });
    return result;
  }
}

New requirements are occurred: create nightly discount promotion