RIT Tech Blog

株式会社RITのエンジニアが知見を共有する技術ブログです。

OpenClosedの原則とは何か

エンジニアの前田です。

概要

OpenClosedの原則について、wikipediaではこう↓あります

ソフトウェア要素(クラス、モジュール、関数など)は、拡張に対しては開いており、修正に対しては閉じているべきである。 わかりやすく説明すると、機能追加や修正の際に対象の箇所以外の既存のソースコードを変更する必要がないプログラムのことです。 ポリモーフィズムを使用する方法が、OpenClosedの原則に従った実装の典型としてよくあげられています。

悪い例から示します。 全自動で飲み物をしてくれるAutoTakeDrinkクラスがあります。 要件として料理に合わせた飲み物を提供する必要があります。 以下、疑似コードです。

class AutoTakeDrink {
  public takeDrinks(foods) {
    const drinks = foods.map(f => {
        if (instansof food === 'ChineseFood') {
          return new ChinaTea();
         } else if (instansof food === 'ItalianFood') {
           return new Wine();
         } else if (instansod food === 'JapaneseFood') {
           return new JapanTea();
         }
       });
    return drinks;
  }
}

上のやり方だと、料理の種類が増えるたびにAutoTakeDrinkクラスのtakeメソッドのif文の分岐を追加する必要が出てきます。 料理の種類を追加がAutoTakeDrinkクラスにも影響を及ぼしてしまうのでOpenClosedの原則に従った実装ではありませんね。

以下にOpenClosedの原則に従った実装を示します。

// まずは各料理クラスが共通のインターフェイス、Foodに従って実装するようにします。
// Foodインターフェイスに従っているクラスは必ずsuitableDrinkメソッドを実装する必要があります。
interface Food {
  public suitableDrink(): Drink
}

class ItalianFood implements Food {
  suitableDrink() {
    new Wine();
  }
}

class ChineseFood implements Food {
  suitableDrink() {
    new ChinaTea();
  }
}

class JapaneseFood implements Food {
  suitableDrink() {
    new JapanTea();
  }
}

class AutoTakeDrink {
  public takeDrinks(foods: Food[]) {
    const drinks = foods.map(f => {
       return f.suitableDrink();
     });
    return drinks;
  }
}

各料理クラスが飲み物を返すsuitableDrinkメソッドを実装することで、 新しい料理を追加した時にAutoTakeDrinkクラスにif文の分岐を追加する必要がなくなりました。 以上が、ポリモーフィズムを使用しOpenClosedの原則に従った実装をした例です。