2023-05-03
リスコフの置換原則の長方形の話
以前リスコフの置換原則の説明でよく出される rectangle/square の例をみて、わかったようなわからんような気持ちになった。
少し整理できた気がするのでメモしてみる。[^1]
[^1]: 元の論文は読んでいない。見たらどうも rectangle の例はなさそうだった。
数学的には、長方形は正方形の抽象である。なぜなら長方形の条件は正方形の条件に全て含まれるから。
よくでてくる例の rectangle[^2] はただの長方形ではなく、「長方形であり、かつ、“上下(or 左右)のみの辺の長さを変更する”操作ができるやつ」(をモデリングしたコード)である。
[^2]: “Clean Architecture”(Martin, 2017)で示される例など。
この「のみの」というのが、コードにおいては(ちゃんと表現するなら)setWidth()
における「処理の前後で height が変化しない」という事後条件となる。
(「rec.setWidth(5); rec.setHeight(2);
の後ではassert(rec.area() == 10)
が成り立つと予期される」という曖昧な言い方は、クリアに捉えれば上記の事後条件に基づいている。)
この rectangle を正方形っぽく特化しようとした場合、以下の両方をみたしたくなる。
- a. 「width と height が常に等しい」 という不変条件
- b.
setWidth()
を継承する- 処理の本旨からして width は変化する
- リスコフの置換原則は事後条件「処理の前後で height が変化しない」を維持することを求める
これらは矛盾するので、リスコフの置換条件を満たす形で正方形っぽく特化することができないことがわかる。
この矛盾を回避するにはそれを生じしめている条件をひとつ除いてやればよい。矛盾を生じしめている条件とは以下である。
- a. square は rectangle を継承する
- b. square は「width と height が常に等しい」 という不変条件をもつ
- c. rectangle の
setWidth()
は「処理の前後で height が変化しない」という事後条件をもつ
解決策として示されることの多い「そもそも square に rectangle を継承させない」というのは、条件 a を除く方法だ。
b は正方形の数学的性質であるからこれを除くことは妥当ではなさそうである。(ただしこれを利用するプロダクトが実は正方形の数学的性質を必要としていなかったら b を除くのもあり。)
c を除くという方向は、プロダクトの要件次第だが十分ありうると思う。この事後条件さえ措かないこととすれば何らの矛盾は生じず、リスコフの置換原則を満たしながら rectangle を継承する square を定義することができるだろう。