SOLID原則:インターフェース分離の原則

インターフェース分離の原則とは?

  • インターフェースクラス(抽象クラス)を使う人が使うものだけ書いておきましょう という原則
  • インターフェースクラスに何でもかんでも書いてはいけません ✋
  • 難しく言うと、不必要な依存関係をなくす ということ
依存関係について詳しく
  • Aを修正するとBも修正が必要になる つまり、BはAに依存している と言える(BはAと依存関係にある)

  • この場合、Aがインターフェース、Bがインターフェースを使う側のソースコードというイメージを持つといいのかもしれません

よくない例

文章だけでは理解できないので、Pythonで書いたコードで例を挙げてみる

動物クラスを作る

以下は、動物クラスのインターフェースとして使う予定

動物であれば、走る, 食べる, 寝る 動作はできますよね 🐈

class Animal:
    """動物クラス."""
    
    def run(self):
        

    def eat(self):
        

    def sleep(self):
        

動物クラスを継承して猫クラスを作る

以下は、具体的にコードを実装して猫クラスを作ってみました

そして、この猫クラスをAさんが使っているとしましょう

class Cat(Animal):
    """ネコのクラス."""

    def run(self):
        print('ネコが走る')

    def eat(self, food: str):
        print(f'ネコが{food}を食べる')

    def sleep(self):
        print('ネコが眠る')

雀クラスを作りたくなった

  • Animalクラスに fly メソッドを追加することにした
class Animal:
    """動物クラス."""
    
    def run(self):
        

    def eat(self):
        

    def sleep(self):
        

    def fly(self): <- 追加したメソッド

以下が雀クラスです

ここだけ見ると良さそうです

class Suzume(Animal):
    """雀のクラス."""

    def run(self):
        print('雀が走る')

    def eat(self,):
        print('雀が食べる')

    def sleep(self):
        print('雀が眠る')

    def fly(self):
        print('雀が飛ぶ')

が、 Animal クラスを変えてしまったので、Aさんが使っているソースコードでエラーが出るようになってしまいました

fly メソッドが追加された と聞いたAさんは、とりあえず実装することにしました

class Cat(Animal):
    """ネコのクラス."""

    def run(self):
        print('ネコが走る')

    def eat(self, food: str):
        print(f'ネコが{food}を食べる')

    def sleep(self):
        print('ネコが眠る')

    def fly(self):
        # ネコは飛ばないので、このメソッドは呼ばない!!!  <- なんであるのかわからないメソッドが追加された
        raise 

Aさんが↑のように、一旦 fly メソッドを追加したことでエラーは起こらなくなりましたが、これはよくないソースコードの書き方です

存在しているのに使ってはいけないメソッドがあるって、なんか気持ち悪くないですか・・・ 🥶 ?

それに、人間はミスをする生き物なので、今後、誰かが間違って使ってしまう可能性もなきにしもあらずですね。 なので、よくないです。

参考: https://kanoe.dev/blog/interface-segregation-principle/

良い例

では、どう修正するのが良いのか

ポイントは、 共通するところはまとめる、共通しないところは分離する です

まず共通するところをまとめる

動物であれば、走る・食べる・寝る は共通

class Animal:
    """動物クラス.

  動物において共通の動作をまとめたクラス.
  """
    
    def run(self):
        

    def eat(self):
        

    def sleep(self):

鳥類は共通して、飛べる という動作があるので、鳥類でまとめる

(ペンギンとかダチョウは、とりあえず見逃してください 🙇‍♀️)

また鳥類は動物なので、Animalクラスで定義した動作もできる → Animalクラスを継承する

class Bird(Animal):
    """鳥類クラス.

  鳥類において共通の動作をまとめたクラス.
  """

    def fly(self):

今回は鳥類でまとめているので、猫クラスを定義するためにも哺乳類クラスも作っておく

哺乳類も動物なので、Animalクラスで定義した動作もできる → Animalクラスを継承する

class Mammal(Animal):
    """哺乳類クラス.

  哺乳類において共通の動作をまとめたクラス.
  """

    def cry(self):
        

では猫クラスを書いてみる

class Cat(Mammal):
    """ネコのクラス."""

    def run(self):
        print('ネコが走る')

    def eat(self, food: str):
        print(f'ネコが{food}を食べる')

    def sleep(self):
        print('ネコが眠る')

    def cry(self):
        print('ネコが泣く')
    
    def purr(self):
        """鳴くメソッド."""
        print('ニャー')
  • 鳴くメソッドに加えて、Animalクラスのメソッドと、Mammalクラスのcryメソッドが猫クラスでは実行できる

雀クラスは、Birdクラスを継承すると良さそう 😊

class Suzume(Bird):
    """雀クラス."""

    def run(self):
        print('雀が走る')

    def eat(self,):
        print('雀が食べる')

    def sleep(self):
        print('雀が眠る')

    def fly(self):
        print('雀が飛ぶ')

    def sing(self):
        """さえずるメソッド"""
        print('ちゅん♪')
  • さえずるメソッドに加えて、AnimalクラスのメソッドとBirdクラスのflyメソッドが雀クラスでは実行できる

こんな感じでクラスを作っていくと、共通部分は使い回せて、独自の部分も作れる構造になる

参考:https://qiita.com/k-penguin-sato/items/86b8262bfbe189fc72c3

インターフェース分離の原則を守るメリットは?

  • インターフェースを使う側に親切 ✨
  • つまり、インターフェースを使う側が、自分に必要な部分だけ考えればいい

  • 上記の例だと、この原則を破っていたソースコードでは、猫クラスを実装したいだけなのに、flyメソッドが存在しているせいで、

    「猫は飛ばないからこのメソッドは呼び出したらエラーになるようにしなきゃ」 

    みたいな無駄な考えとソースコードを生んでしまう

  • インターフェース分離の原則を守った修正後のソースコードでは、Mammalクラスが用意されているので猫のことだけ考えて、インターフェースを使うことができる

他の原則にも言えますが、バグも少なくなりますね 😄

あとは機能拡張やソースコードの修正など、メンテナンスもしやすくなりそう。

どの原則も極論、プログラムが正常に動く可能性を高める 原則とも言えそうですね。