4-19:継承のために設計および文書化する、でなければ継承を禁止する
要点
継承は強力だが脆い。サブクラスがスーパークラスの実装の内部仕様に依存すると、スーパークラスの変更でサブクラスが壊れる。公開 API が増え、保守や互換性のコストが跳ね上がる。だから「拡張を公的にサポートする」か「完全に禁止する」か、どちらかにすべき。
悪い例
public class Parent {
public Parent() {
init(); // 危険:オーバーライド可能なメソッドをコンストラクタで呼ぶ
}
public void init() { /* 初期化 */ }
}
public class Child extends Parent {
private final Resource r = new Resource();
@Override
public void init() {
r.doSomething(); // r はまだ初期化されていない可能性がある -> NPE
}
}
問題点
- スーパークラスのコンストラクタがオーバーライド可能なメソッドを呼んでいる
→ サブクラスのフィールドが未初期化の状態で呼ばれる危険。 - スーパークラスがどのメソッドを「安全に」オーバーライドできるか文書化していない
→ サブクラス実装が壊れる。
良い例
設計どおりに拡張ポイント(hooks)を提供し、仕様を文書化する例:
/**
* ドキュメントに「サブクラスはこの hook を実装してよい。ただしコンストラクタではなく
* onInit() が呼ばれるタイミングで安全に使える」と明記する。
*/
public abstract class Framework {
public final void start() {
// 固定の初期化手順
initCore();
// hook を呼ぶ(安全なタイミング)
onInit();
}
private void initCore() { /* 基本初期化 */ }
/**
* 拡張ポイント: サブクラスはここを実装できる。
* implSpec: onInit は start() の中で呼ばれ、すでに基盤が初期化済みであることを保証する。
*/
protected abstract void onInit();
}
ポイント
- start() は final にして処理順を固定(サブクラスが勝手に手順を壊せない)。
- 拡張ポイント onInit() は protected にし、いつ呼ばれるか/どの条件下で安全かを Javadoc(@implSpec など)で明記する。
- サブクラスはドキュメントに従えば安全に拡張できる。
継承を禁止する/制限する方法
ライブラリ作成者は継承を禁止したいことが多い。
やり方:
1. finalクラスにする(一番簡単)
public final class Utility { ... }
2. コンストラクタをprivateにして静的ファクトリを用意(派生禁止 + インスタンス制御)
public class Foo {
private Foo() { }
public static Foo of(...) { return new Foo(...); }
}
3. パッケージプライベート(default)にして外部から継承不能にする
class InternalHelper { ... } // same package only
4. Java17+のsealedクラスで制御(明示的に許可されたサブクラスのみ)
public sealed class Base permits AllowedSub1, AllowedSub2 { ... }
5. ドキュメントで「継承はサポートしない」旨を明記し、可能ならコンパイル時に final を使う(宣言的に禁止)。
継承を「設計して文書化」する際の必須項目
ライブラリで継承を許すなら、最低でも次を文書化しておくこと:
- いつ/どの順序でサブクラスのメソッドが呼ばれるか(コンストラクタ後か、初期化完了後か等)。
- どのメソッドが拡張ポイント(override して良い)か(そしてその契約)。
- サブクラスで守るべき不変条件。
- スレッド安全性の契約(呼び出しはシングルスレッドか?マルチスレッドで呼ばれるか?)。
- 例外処理の方針(サブクラスが例外を投げてよいか、投げる場合どう扱うか)。
- super のメソッドは必ず呼べるか/呼ぶべきか(呼ぶ順序やタイミング)。
- 将来の互換性についての注意(どの部分は安定 API で、どれは将来変更される可能性があるか)。
まとめ
継承を許すなら設計と契約を明確にして文書化し、そうでなければ継承を禁止してユーザーに不確定な拡張をさせないこと。
これにより将来の保守性・互換性・安全性が格段に向上する。