拡張性/柔軟性を獲得する章

3.5. 継承の良い例

『目的』

継承が適しているパターンを知りましょう。

『After』

継承が適切なのは次のようなケースです。
サブクラスとスーパークラスが同じプログラマの管理下にあって パッケージ内の継承を使用する場合
同じプログラマの管理下にあればどのような影響を及ぼすかは把握できているはずなので、 具象クラスを継承しても問題ありません。

パッケージをまたがってしまうと、どのクラスもサブクラス化可能になってしまいますから危険です。 あずかり知らぬところでサブクラスが作られたりして、それへの対応でスーパークラスに変更が入り、 自分のためのサブクラスがとばっちりで悪影響を受けるかもしれません。

拡張前提のクラスであれば、パッケージをまたがっていても話は別です。
スーパークラスが拡張前提に設計されていて拡張をするために文書化されている場合
抽象クラスはまさに継承前提なので言わずもがなです。 メソッドのオーバーライドの影響は、正確に文書化しなければなりません。
オーバーライド可能メソッドが呼び出しメソッド内部でどのように使われるのか (呼び出し順番や呼び出し後の影響)を書いていないと、先ほどのHashSet#addAllのように 誤った結果を得てしまいかねません。
「X is a type of Y」(XはYの一種です)の関係がある場合
Javaライブラリを見るとこの原則に従っていない物があります。
たとえば、StackとVectorです。

スタック(後入れ先出し)はベクター(インデックス指定できる可変長配列)の一種ではないので、 本来は拡張すべきではありません。にもかかわらず、StackはVectorを継承しています。

そのおかげでおかしなことが起こります。

Stackはあくまでも後入れ先出しの操作だけを目的としたクラスのはずです。 でも、Vectorを継承したために、本来必要のないget(index)メソッドなども外部に公開されています。 スタックのはずなのに、インデックス指定で入れたり取り出したりという操作までできてしまいます。

これではStack本来の動作から逸脱してしまうので、使い方に気を遣わねばなりません。

『まとめ』

継承は再利用性を高めることができますが、カプセル化を破ることが問題としてあります。

サブクラスとスーパークラスが「is a type of」の関係であるときは継承は適切です。 ただしそうであっても、拡張前提に設計されていないスーパークラスに対して パッケージをまたがって継承してしまうと、もろさを生み出してしまうかもしれません。

継承の代わりにコンポジションと転送の組み合わせを使うことで回避できる場合もあります。

< 前のページへ

Pagetop