nonoのポートフォリオサイト

SOLIDの原則

SOLIDの原則3 リスコフの置換原則

クラス、モジュール、関数などのソフトウェアの部品は拡張に対しては開いており、修正に対しては閉じていなければならないとする原則です。


目的

スーパークラスの仕様を理解すれば、それを継承したサブクラスは中身を全てを確認しなくても利用することができる。
(拡張性、保守性の向上)
サブクラスとスーパークラスの間で差異(実行できるものとできないもの)があると、 サブクラスを使うために、サブクラスを全て理解する必要がでてしまう。

関数φ(x):
 Tクラスのインスタンスx で実行できる場合、
 Tクラスのサブクラスのインスタンスy でも実行できること

サンプルコード


    # liskov_substitution.py
    """
    スーパクラスで使えるものは、サブクラスでも使えるようにしておく
    """
    
    class Rectangle(object):
        """ 長方形 """
        def __init__(self, width, height):
            self._width = width
            self._height = height
    
        # propertyとgetter, setterメソッドはセットで考える
        # https://naruport.com/blog/2019/8/27/python-tutorial-class-property-getter-setter/
        @property
        def width(self):
            return self._width
    
        @width.setter
        def width(self, width):
            self._width = width
    
        @property
        def height(self, height):
            self._height = height
    
        @height.setter
        def height(self, height):
            self._height = height
    
        def calcurate_area(self):
            return self._width * self._height
    
    
    class Square(Rectangle):
    
        def __init__(self, size):
            self._width = self._height = size
    
        """親クラスのセッターをオーバライドする(プロパティの再定義がいらない)"
          また、pythonの使用上、getterも定義しなければならなくなるとのこと
        """
        @Rectangle.width.setter
        def width(self, size):
            self._width = self._height = size
    
        @Rectangle.height.setter
        def height(self, size):
            self._width = self._height = size

作成したクラスをメインロジックの中で以下のように使います。


def print_area(obj):
    change_to_width = 10
    change_to_height = 20

    # このままではリスコフの置換原則を満たしていない
    # そのため、Squareが入ってきているのに、10*20をしてしまう。
    obj.width = change_to_width
    obj.height = change_to_height

    # 修正方法1(Squareの場合、強引に値を修正する)
    if isinstance(obj, Square):
        # このプログラム内で、squareオブジェクトに合わせる
        change_to_width = change_to_height

    # 修正方法2(考え方のみ)他のやり方としては、Reactangleの継承ではなく、もっと抽象的なクラスを作成して
    # print_areaはSquareで実行できないようにする、という方法も考えられる。

    print('Predicted Area = {}, Actual Area = {}'.format(
        change_to_height * change_to_width,
        obj.calcurate_area()
    ))

if __name__ == '__main__':

    rc = Rectangle(2, 3)
    print_area(rc)

    sq = Square(5)
    print_area(sq)