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

項目17 継承のために設計および文書化する、でなければ継承を禁止する

項目16ではHashSetクラスの継承を例として、親クラスが自身のクラスのメソッドを自己利用していることを知らないまま継承を行ったサブクラスが想定外の結果に悩まされる危険があるということを述べていました。本書ではこういったオーバーライド可能なメソッドの自己利用を文書化しなければならないと言っています。つまり、API提供者は提供するクラスのメソッドをサブクラス化することへの影響をきちんと文書化するべきということです。

自己利用を文書化する必要があるのは「publicやprotectedであるメソッド、あるいはコンストラクタ」です。

JavaDocでは慣例的に、そういったオーバーライド可能なメソッドを利用しているメソッドに対して、「この実装は(The implemtntaton)」という言葉で始まっているそうです。本書と同じく、java.util.AbstractCollectionクラスのremove()メソッドのドキュメントを見てみます。

下記はその実装です。

AbstractCollectionのremoveメソッドがiteratorメソッドを実装し、そのIteratorインターフェースのremoveメソッドを実装する必要があることを明確に記述しています。このメソッドをオーバーライドする場合、そのクラスが生成するIteratorインターフェースのremoveメソッドが影響するんだな、ということが見て取れるはずです。このように安全なサブクラス化を行ってもらうためには実装の詳細を文書化する必要があります。これは継承がカプセル化を破っていることの不幸な結果だということです。

ここまでは項目16の延長です。ここから継承のための設計をするには実装の詳細を文書化することに気を付ければいいだけではないよ、という話になります。

クラスは、賢く選択されたprotectedのメソッドの形で、クラス内部の動作へのフックを提供しなければならないかもしれません

これも本書の例を見てみます。java.util.AbstractListクラスのremoveRangeメソッドです。

このremoveRangeメソッド、同じクラスのclearメソッド内部で使用されています。そのこと自体はこのListの最終的な利用者には関係ありません。ただし、このクラスをオーバーライドする開発者は気を付ける必要があります。うまくオーバーライドしないとパフォーマンスにひどく影響するかもしれないからです。つまりよりよい実装をオーバーライド実装者が選択できるようにするためprotectedにして公開し(賢く選択されたprotectedのメソッドの形で)、この場合はclearメソッドのパフォーマンスを改善するためにclear内部で使用しているremoveRangeメソッド(クラス内部の動作へのフック)を提供することを言っています。そして、その旨を文書化しています。

この項目の前半は、継承を設計するには継承することで影響する実装の詳細を文書化することと、時にはオーバーライド実装者に(あるときはパフォーマンス改善などの)選択を委ねるために継承させることがあるかもしれない、という話をしています。じゃあ、継承することの影響や賢い選択を見つけるにはどうすればいいの?という答えが次の話です。

継承のために設計されたクラスをテストする唯一の方法は、サブクラスを書くこと

実際に継承してみることが唯一の方法ということです。だいたい3つぐらいサブクラスを書けばいいと本書では言っています、それもそのうち1つか2つはスーパークラスの作成者以外の人が書くといいとか。機会があればそういう風にテストしたいですね。

ここからは具体的にクラスが継承を可能にするための制約の話が続きます。まとめます。

  • コンストラクタはオーバーライド可能なメソッドを呼び出してはならない
  • 継承のために設計されたクラスにCloneableインターフェースやSerializableインターフェースを実装するのはいい考えではない
  • 2番目に関し、オーバーライド可能なメソッドでcloneおよびreadObjectメソッドを呼び出してはいけない
  • Serializableインターフェースを実装する場合に、そのクラスがreadResolveメソッドやwriteReplaceメソッドを持っている場合、それらのメソッドはprotectedにしなければならない

こういった安全に継承させるクラスの設計ができない場合はサブクラス化を禁止すべきです。それはfinalなクラスとするか、あるいはprivateかパッケージプライベートなコンストラクタしかもたないクラスにして、オブジェクトの生成はstaticファクトリメソッドで行うことです。

しかしどんどん話が難しくなってきますね・・・。

広告
  • LINEで送る