リスコフの置換原則(Liskov Substitution Principle)
リスコフの置換原則(LSP)は、次のように要約されている:
派生型はその基本型と置換可能でなければならない。
アジャイルソフトウェア開発の奥義 第2版 p.143-144
簡単にいえば、派生クラスは基本クラスと互換性がなければならないということである。
適当な計算を行うクラスがあるとしよう。
class Calculator
def calculation(value)
if value.is_a?(Integer)
raise ArgumentError.new('整数以外は指定できません!')
end
result = calc # いろんな計算をしてresult変数に格納する
return result.to_i
end
end
このCalculator#calculation
メソッドは引数としてInteger
の値を受け取る。また、結果としてArgumentError
例外が発生するかInteger
の値を返す。
このクラスを利用しているコードは以下のようになるだろう。
# 引数のcalculatorはCalculatorかその派生クラスのインスタンス
def foo(calculator, value)
result = calculator.calculation(0) + calculator.calculation(value)
# resultがIntegerであることに依存したコード...
rescue ArgumentError => e
# 例外処理
end
派生クラスとして以下の場合を考えてみる。
class CalculatorA < Calculator
def calculation(value)
# Integer以外なら0扱い
value = 0 unless value.is_a?(Integer)
result = calc # いろんな計算をしてresult変数に格納する
return result.to_i
end
end
このCalculatorA#calculation
メソッドは引数として任意の値を受け取る。また、結果として例外は発生することなくInteger
の値を返す。
このCalculatorA
クラスのインスタンスは、先ほどのfoo
メソッドを改修することなくfoo
メソッドに渡すことができる。
これは、基本クラスに対して派生クラスの入力の範囲が広がり(Integer
だけから任意の値になった)、出力の範囲が狭まっている(ArgumentError
例外が発生しなくなった)ためである。
逆に以下のような派生クラスを考えてみる。
class CalculatorX < Calculator
def calculation(value)
if value.is_a?(Integer)
raise ArgumentError.new('整数以外は指定できません!')
end
# いろんな計算をしてresult変数に格納する
result = 100 / value # ゼロ除算の可能性がある
return result.to_i
end
end
このCalculatorX#calculation
メソッドは引数として0以外のInteger
の値を受け取る。また、結果としてArgumentError
例外かZeroDivisionError
例外が発生するかInteger
の値を返す。
このCalculatorX
クラスのインスタンスは、先ほどのfoo
メソッドを改修しなければfoo
メソッドに渡すことができない。必ず、ZeroDivisionError
例外が発生してしまうし、ZeroDivisionError
例外の捕捉をしていないためプログラムが異常終了してしまうだろう。
CalculatorX
クラスの場合はZeroDivisionError
例外が増えるという形で出力が増えているが、コード値を返すメソッドでコード値の種類が増えるようなケースでも同じである。例えば、基本クラスではA
、B
、C
のいずれかしか返さないのに、派生クラスでX
も返すようになると次のコードは壊れてしまう。
def bar(object)
case object.code
when 'A'
# ...
when 'B'
# ...
when 'C'
# ...
end
end
まとめ
- リスコフの置換原則とは簡単にいえば、派生クラスは基本クラスと互換性がなければならないということ
- 基本クラスに対して派生クラスの入力の範囲が同じか広がり、出力の範囲が同じか狭まっているならリスコフの置換原則に則っている
リスコフの置換原則を順守することは必須である。リスコフの置換原則に違反したコードはその時点で不具合が発生しているか、不具合のタネを抱えてしまっている。