Effective Javaを読むチャレンジ-項目16-

項目16 継承よりコンポジションを選ぶ

結論から言うと、クラスを拡張しようとするとき、拡張するクラスがスーパークラスと本当のサブタイプ(is-a関係)にある場合だけ継承を選び、そうでない場合はコンポジションにしなさいよという話。そして継承はカプセル化を破る問題があると言っています。では本文を読んでいきます。

継承はコードを再利用する強力な方法

Javaは、スーパークラスのフィールドやメソッドをサブクラスでも使えます。これがコードを再利用する強力な方法という意味です。

ついでに言うと、Javaの継承はポリモーフィズム(多態性)という強力な特性も併せ持っています。スーパークラスの型にサブクラスの型を代入でき、その実行時の振る舞いは代入されたインスタンスによって決まるというものです。コードの再利用とポリモーフィズムがJavaの継承の特性です。

メソッド呼び出しと異なり、継承はカプセル化を破る

この言葉、あまり理解できないでいます。この言葉の元になっている論文を見てみると、スーパークラスで定義されたインスタンス変数へのアクセスを許可すると、カプセル化を損なうとありました。確かにそうでしょうが、本書の項目13のように、すべてのフィールドをprivateにすればいいのでは?と思ったのですが・・・。この論文はJavaが出来る前のものなので、一般的なカプセル化の話として出した言葉でしょうか。

また、メソッド呼び出しと異なり、という部分が第一感で一番飲み込みづらいですね。拡張したいクラスを継承せずにクラスのメソッドを呼び出すと、確かにカプセル化は破りませんが・・・そういう意味ではない?

この話を言い換えて、の次の話です。

コード管理者が同じではない別パッケージの具象クラスを継承する場合は危険が生じます。自分の知らない間にスーパークラスに変更があったせいで、そのサブクラスが正常に動作しなくなることがあるということです。で、この正常に動作しなくなるという例が後に続きます。

それは、スーパークラスのメソッドが自身のクラスのメソッドを自己利用していることを知らずにサブクラスを継承したために、想定と異なる結果が生じたという例です。スーパークラスの実装の詳細を知らないとサブクラスではうまく動作しないし、スーパークラスの実装が変わってさらに異なる結果になることもあります。つまり、サブクラスがスーパークラスの実装の詳細に依存しているということです。

継承はカプセル化を破る、という話を言い換えると、なぜサブクラスがスーパークラスの実装の詳細に依存するという話になるのか。本書ではカプセル化=情報隠ぺいとしています。情報隠ぺいはとはAPIと実装を分離して実装の詳細を隠ぺいすることです。だから実装の詳細に依存する継承は情報隠ぺい出来ているとは言えない=カプセル化を破っている、という論法でしょうか。それならここまでの話はすんなり喉元を通ります。

サブクラスでのもろさの原因の1つは、スーパークラスは後のリリースでメソッドを追加できること

例えが難しくて・・・実際にサンプルを書いて試したい所です。HashtableとVectorをコレクションフレームワークに加えるときに、このことによって起きるセキュリティホールを塞ぐことになったとありますが、具体的に何をしたのかよく分かりません。

コンポジション

継承の問題を解決するためにコンポジションを使います。コンポジションとは、以下のようなクラスの実装です。

  • 拡張したいクラスをprivateフィールドに持つ
  • 既存のクラスのメソッドを呼び出すだけのメソッドを必要なだけ用意する(転送メソッド)

具象クラスを直接継承するのではなく、上記のような転送クラスを継承して拡張することになります。ここで、既存のクラスのメソッドを必要なだけ用意する、というところがポイントです。スーパークラスがメソッドを追加しても、転送クラスにそのメソッドが用意されていないので、サブクラスの利用者がサブクラスでオーバーライドしていないスーパークラスのメソッドを呼び出すという事態にもなりません。

コンポジションはラッパーとも呼ばれ、GoFのデコレータパターンとしても知られています。

便利なコンポジションですが、SELF問題という欠点もあるそうです。この説明も難しくてピンときてません。

噛み砕けない説明が多く、コンポジションが素晴らしいものだと素直に感動しづらいです。難しい。。。

広告
  • LINEで送る