リスコフの置換原則について(SOLID編)
リスコフの置換原則は、オブジェクト指向設計の原則の一つになります。そして、SOLIDとはその数々の原則から選ばれた5つの原則の頭文字をとったものとなります。
今回は、SOLIDのLにあたるリスコフの置換原則(LSP: Liskov Substitution Principle)について説明していきます。
リスコフの置換原則とは
まず定義の方から説明していきます。
「SがTの派生型ならば、関数fがT型のオブジェクトxの特性を真と立証するならば、関数fはS型のオブジェクトyの特性にも真でなければならない」
wikipediaより引用「SOLID」, https://ja.wikipedia.org/wiki/SOLID
難しい説明に見えますが、要は「スーパークラス(T)をそのサブクラス(S)で置き換え可能でないといけないよ」ということになります。
ではこの原則を守ると何が嬉しいのかというと、「そのクラス間の継承関係は正しい」ということを保証できるという点にあります。
継承はよく委譲などと比較され、その使い方が難しいことから「初心者は継承を使うな」のような記事もしばしば散見されます。
ですので、どうすればリスコフの置換原則を満たすのか、ということを理解し無闇な継承を避けてコーディングできるようになることを目指します。
リスコフの置換原則を満たす条件
ここで少し、契約による設計(DbC: Design by Contract)と呼ばれるプログラミング技法について説明します。
契約による設計とは、
「プログラムコードの中にプログラムが満たすべき仕様についての記述を盛り込む事で設計の安全性を高める技法。」
wikipediaより引用「契約プログラミング」https://ja.m.wikipedia.org/wiki/
ここで言う「プログラムが満たすべき仕様」が「リスコフの置換原則を満たす条件」を説明する際に必要な概念となります。
まず一つ目が、「事前条件」になります。これはベイズ統計学などでも耳にする単語ですが、ここでは、「サブクラスが呼び出されたときに満たしておかなければいけない条件」と理解してください。引数がこれにあたります。
そして二つ目が、「事後条件」になります。これは、事前条件に対して「サブクラスが終了するときに満たしておかなければいけない条件」となります。戻り値がこれにあたります。
最後に「不変条件」になります。これは、「ある処理の間、真偽値が常に真である述語」のことを指します。
例えば、あるサブクラスXの中で変数xに様々な処理を加えたとします。ただこの処理は整数の足し算と引き算に限定したとします(変数xの初期値も整数とします)。
すると「xは整数である」と言う述語はサブクラスXに対する不変条件であると言えます(整数同士の足し算・引き算で結果に小数点がつくことはないため)。
この三つの条件を使って、リスコフの置換原則を満たすための条件を以下に示します。
1. サブクラスの方が事前条件が緩い(反変性)
2. サブクラスの方が事後条件が厳しい(共変性)
3. サブクラスでも不変条件が保持されている(不変性)
4. サブクラスで発生する例外は、スーパークラスで発生する例外と同じ、
または、その派生型でなければならない
以上を満たしているとき、リスコフの置換条件を満たしているため、そのクラス間の関係は正しい 継承関係になっていると言えます。
ちなみに、共変・反変とは数学の圏論由来の用語だそうですが、そこまで難しい話ではないです。
例えば、スーパークラスをAnimal、そのサブクラスをDogとし、その関係性をベン図を用いて表します。
これに対して、事前条件についてはDogの方が条件が緩く、事後条件ではDogの方が条件が厳しくなります。ですのでその関係性は以下のようになります。
事前条件は右側の反変性に対応し、事後条件は左側の共変性に対応します。
ややこしい説明が続きましたが、結局はスーパークラスであるAnimalをDogに置き換えたと考えた際、
入力時にはAnimalと同等もしくはそれよりも大らかでなければならず、
出力時にはAnimalと同等もしくはそれより厳しくなければなりません。
そうしないと、入出力の前後でエラーが起きる可能性が出てくることになってしまいます。
まとめ
今回は、リスコフの置換原則について説明しました。
分かりにくい点、間違っている点などありましたらコメントいただけると幸いです。
この記事が気に入ったらサポートをしてみませんか?