📘

SOLID原則 ◆オープン・クローズドの原則◆

2022/01/09に公開

オープン・クローズドの原則とは

SOLID原則の、Open Closed Principle(以下:OCP)のことで、「ソフトウェアの構成要素は拡張に対して開いていて、修正に対して閉じていなければならない」という原則です。

言い換えると、ソフトウェアの振る舞いは、既存の成果物を変更せず拡張できるようにすべきであるということです。

以下の仕様になっているサンプルコードを見ながら実際にGoodコードとBadコードを見ながら、理解を深めてみます。

- ある会員制ECサイトの決済システムで、会員のグレードによって割引がされる
- DiscountManagerクラスのcalculateメソッドで商品の請求額を計算する

🙅Badコード

よく見るような処理ですが、これはOCPに反しています。
例えば、今ある会員グレードに種類を足したい場合 DiscountManager クラスのswitch文に分岐を足すことになります。
それは 既存の成果物を変更せず  に反しています。
凡ミスして誤ったコードを書いてしまったりするリスクがあり、既存の成果物が再現できなくなるかもしれません。

enum Grade {
  PLATINUM = 'Platinum',
  GOLD = 'Gold',
  SILVER = 'Silver',
  BRONZE = 'Bronze',
}

class Member {
  constructor(public grade: Grade, public totalAmount: number) {}
}

class DiscountManager {
  calculate(member: Member) {
    let discountAmount: number;

    // 会員グレードによって割引額を決定
    switch (member.grade) {
      case Grade.PLATINUM:
        discountAmount = member.totalAmount * 0.3;
        break;
      case Grade.GOLD:
        discountAmount = member.totalAmount * 0.2;
        break;
      case Grade.SILVER:
        discountAmount = member.totalAmount * 0.1;
        break;
      case Grade.BRONZE:
        discountAmount = member.totalAmount * 0.05;
        break;
    }

    return member.totalAmount - discountAmount;
  }
}

const memberA = new Member(Grade.PLATINUM, 10000);
const memberB = new Member(Grade.GOLD, 15000);
const memberC = new Member(Grade.SILVER, 30000);
const memberD = new Member(Grade.BRONZE, 7000);


const discountManager = new DiscountManager();
console.log(discountManager.calculate(memberA));
console.log(discountManager.calculate(memberB));
console.log(discountManager.calculate(memberC));
console.log(discountManager.calculate(memberD));

🙆‍♂️Goodコード

会員のインターフェースIMemberを作成して、それを実装(implements)したPlatinum・Gold・Silver・Bronzeクラスがあります。
そして、DiscountManagerクラスからはswitch文が消えて、calculate()を呼ぶだけになりました。
クラスが増えましたが、だいぶシンプルになりましたね。
そして何より、「新しくグレードを追加したい」時にその分のクラスを追加すればいいので、 既存の成果物を変更せず 拡張できます。

enum Grade {
  PLATINUM = 'Platinum',
  GOLD = 'Gold',
  SILVER = 'Silver',
  BRONZE = 'Bronze',
}

class IMember {
  grade: Grade;
  totalAmount: number;
  calculate: () => number;
}

class Platinum implements IMember {
  private DISCOUNT = 0.3;
  grade = Grade.PLATINUM;

  constructor(public totalAmount: number) {}

  calculate() {
    return this.totalAmount * this.DISCOUNT;
  }
}

class Gold implements IMember {
  private DISCOUNT = 0.2;
  grade = Grade.GOLD;

  constructor(public totalAmount: number) {}

  calculate() {
    return this.totalAmount * this.DISCOUNT;
  }
}

class Silver implements IMember {
  private DISCOUNT = 0.1;
  grade = Grade.SILVER;

  constructor(public totalAmount: number) {}

  calculate() {
    return this.totalAmount * this.DISCOUNT;
  }
}

class Bronze implements IMember {
  private DISCOUNT = 0.05;
  grade = Grade.BRONZE;

  constructor(public totalAmount: number) {}

  calculate() {
    return this.totalAmount * this.DISCOUNT;
  }
}

class DiscountManager {
  calculate(member: IMember) {

    return member.totalAmount - member.calculate();
  }
}

const memberA = new Platinum(10000);
const memberB = new Gold(15000);
const memberC = new Silver(30000);
const memberD = new Bronze(7000);


const discountManager = new DiscountManager();
console.log(discountManager.calculate(memberA));
console.log(discountManager.calculate(memberB));
console.log(discountManager.calculate(memberC));
console.log(discountManager.calculate(memberD));

参考資料

Clean Architecture

Discussion