>>もしくは、間違ってfly()させちゃいけないのなら
>>例外を投げるのもありかなと思います。
>これは LSP 違反だと思うのです。
>
http://www2.ocn.ne.jp/~yamagu/object/LSP-J.pdf
>の Rectangle と Square の例[本当の問題]と同等な問題を内在させることになりそうです。
>さて、こういう場合に「そもそも同一視すべきだったか」を私は問いたいのです。
ここで、前提条件として以下のものを考えます。
「struct bird { virtual void fly()=0; };
に対し、birdを継承したbat(こうもり)クラスの実装も
クライアント側の処理もうまくいった。
ここで新たにpenguin(ペンギン)クラス(処理)を追加する必要が
生じた。」
この時の設計として、penguinクラスとbatクラスを
同一視(例えば、両方ともbirdから継承)すべきかどうかを考えてみます。
私の前回の発言のように、penguinクラスとbatクラスを
同一視して、penguinのfly()の実装で例外を投げようとすると、
birdを扱うクライアント側の処理でbatとpenguinを
同一扱いできないので、LSPに違反します。
つまり、penguinクラスのfly()の実装でどうしても例外を
投げたいのであれば、明らかに設計ミスで、birdクラスで
例外を投げるように設計しておかなければなりません。
しかし、penguinのfly()を、batクラスのfly()とクライアント側で
同一視できるように実装しておけば問題ありません
(例えば、fly() {}のような空実装など)。
すなわち、774RRさんが、[745]の2でおっしゃってるように、
既に実装されているクライアント側の処理にマッチするように
penguinのfly()を実装すれば問題ありません。
しかし、現実的には全てのクライアント側の処理を調べるのは
現実的ではないので、LSPを頼りに、
「penguinのfly()の振る舞いがbatのfly()の振る舞いと同等である」
という「常識的な」判断ができれば、クライアント側でbatとpenguinが
同一視できると判断しても妥当と言えるかもしれません。
また逆に、LSPに違反した継承を行った場合でも、
クライアント側で両方に共通した使い方のみで実装してあげれば
問題ありません
(もちろん、その後、LSPに違反するような実装をクライアント側の
処理で行う実装を追加すれば問題が起きるので設計としては
かなり危険な設計ですが)。
また、774RRさんの[743]での以下の疑問
>さて、こういう場合に「そもそも同一視すべきだったか」を私は問いたいのです。
は、「既に存在するクライアント側の処理がどうなっているか」と
「penguinのfly()の実装をどうしたいか」の両方に依存しているので
常に正解の設計方法論はないと私は思います
(クライアント側の処理をあらかじめ規定するには、Design by Contractとかに
類する機能が必要だと思います)。
ここで、私の出た結論としては、私自身かなり混乱しているのですが、
つまり、penguinクラスをbirdから派生させてbatと同一視すべきかどうか
という設計を迫られたとき、これがLSPに当てはまるかどうかを
考えようとして、
「penguinのfly()の振る舞いが、batのfly()の振る舞いと同一か」
という疑問を持ったとき、
penguinのfly()を実装しようとした時に、どう転んでも
クライアント側の実装がbatのfly()と同一視しかできない実装になっている
のであれば、全く問題ない訳で、
クライアント側の実装により、少しでも同一視できない可能性があるなら
そういう設計すべきでない訳で、
つまり、それをどう判断できるかと言うと、クライアント側のコードの
予想がつかないため、そんなに単純にLSPで判断できないような気が
してきました(クラスが単純ならできる場合もありそうです)。
ここでは、fly()関数1つのみを考えている訳ですが、実際は、クラスは
複数の関数を持つ場合がほとんどなので組み合わせるともっと複雑になるし。
結局、同一視すべきかは、LSPはかなり参考にはなるが、
どんな局面でも通用する設計方法は存在せず、「実装依存」な気がします。