Python3で学ぶSOLID原則〜依存性逆転の原則〜


今回はSOLID原則の「依存性逆転の原則」についてです。
(恥ずかしながら初勉強です・・・自身の整理のためにもまとめていこうと思います)

概要

  • ソフトウェア開発者のRobert C. Martinの2000年の論文で発表されたものが元になっている設計思想です。
  • Robert C. Martinは有名なプログラマーでCleanシリーズの書籍の著者でもあります(巷ではボブおじさんと呼ばれているらしいです)

以下、5つの原則の頭文字をとってSOLID(ソリッド)原則と呼ばれています。プログラムの拡張性、保守性、可読性を高くするために生まれました。

  1. 単一責任の原則(Single Responsibility Principle)
  2. 開放閉鎖の原則(Open/Closed Principle) *オープン・クローズドの原則とも呼ばれています。
  3. リスコフの置換原則(Liskov Substitution Principle)
  4. インタフェース分離の原則(Interface Segregation Principle)
  5. 依存性逆転の原則(Dependency Inversion Principle)

依存性逆転の原則(Dependency Inversion Principle)

一言でいうと、「抽象クラス(インタフェースを持つクラス)に依存するべきで、抽象クラスを継承したクラス(詳細クラス)に対して依存させてはいけない」です。依存関係を逆転させるな(切り離せ)ってことです。

以下Wikipediaからの引用です。

オブジェクト指向における従来の依存関係とは、上位モジュールから下位モジュールへの方向性であり、仕様定>義を担う上位モジュールを、詳細実装を担う下位モジュールから独立させて、各下位モジュールを別個保存する>というものだったが、それに対して依存性逆転原則は以下二点を提唱している[2]。

上位モジュールはいかなるものも下位モジュールから持ち込んではならない。双方とも抽象(例としてインターフェース)に依存するべきである。
"High-level modules should not import anything from low-level modules. Both >should depend on abstractions (e.g., interfaces)."[3]
抽象は詳細に依存してはならない。詳細(具象的な実装内容)が抽象に依存するべきである。
"Abstractions should not depend on details. Details (concrete >implementations) should depend on abstractions."[3]

守ると何が良いか?

  • 機能拡張がしやすくなります。

コードで説明します(Python3)

まず、依存性逆転の原則が満たされてないコードを書きます。

いま店クラス(Store)、食料品店クラス(GgroceryStore)、お客クラス(Customer)があるとします。

class Store:
    def __init__(self, item):
        self.item = item

class GgroceryStore:
    def sell(self, store: Store):
        print('{}を売る'.format(store.item))

class Customer:
    def shopping(self, store: Store):
        grocery = GgroceryStore()
        grocery.sell(store)

store = Store('ジャガイモ')
customer = Customer()
customer.shopping(store)

食料品店クラスは店クラスに依存していますが、店クラスは抽象クラスではなく実態クラス(詳細クラス)です。
また、お客クラスのメソッドで食料品店クラスを読んでいるので複雑な関係になっています。

依存性逆転の原則を守る

from abc import ABCMeta,abstractclassmethod

class Store(metaclass=ABCMeta):
    @abstractclassmethod
    def sell(self):
        pass

class GgroceryStore(Store):
    def __init__(self,item):
        self.item = item

    def sell(self):
        print('店は{}を売る'.format(self.item))

class Customer(metaclass=ABCMeta):
    @abstractclassmethod
    def shopping(self):
        pass

class CustomerA(Customer):
    def __init__(self, store:Store):
        self.store = store

    def shopping(self):
        self.store.sell()

grocery_store = GgroceryStore('ジャガイモ')
customer = CustomerA(grocery_store)
customer.shopping()

店クラス(Store)を抽象クラスに変更し継承するようにしました。これで詳細クラスが抽象クラスに依存するになりました。抽象クラスに依存する(インターフェースに依存する)ようにしておけば、食料品以外の店が増えてもクラスを追加するだけ対応できるようになります。