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クラスが用意されているので猫のことだけ考えて、インターフェースを使うことができる
他の原則にも言えますが、バグも少なくなりますね 😄
あとは機能拡張やソースコードの修正など、メンテナンスもしやすくなりそう。
どの原則も極論、プログラムが正常に動く可能性を高める 原則とも言えそうですね。