継承が好きなあなたへ──それ、本当に委譲じゃダメですか?

Javaでクラス設計をしていると、
「この共通処理は継承でまとめよう」「親クラスでロジックを再利用しよう」
という判断はよく見かけます。継承はコード量を減らせて一見スマートに見える選択肢です。

でも、それあとから困ったことになっていませんか?

  • テストで一部のロジックを差し替えたいのに継承してるからできない
  • 変更したくない振る舞いまで override されて意図が崩れた
  • ライブラリの親クラスに制約があって思うように動かない

こうしたトラブルの原因は、継承という構造が“構造を再利用する方法”ではなく、“制御の支配関係”であることに起因します。

継承は「体を合体させる」、委譲は「パーツをくっつける」

継承:一体化ロボット構造(合体しすぎて外せない)

class SuperRobot extends BaseRobot {
    @Override
    void attack() {
        // 特殊な攻撃方法に変更
    }
}
  • SuperRobotとBaseRobotが一体化している
  • 一部だけ差し替えるのが難しい
  • BaseRobot側の変更がSuperRobot全体に波及する

委譲:パーツで構成されるロボット(交換・組み換えが自在)

class SuperRobot {
    private final Weapon weapon;

    public SuperRobot(Weapon weapon) {
        this.weapon = weapon;
    }

    public void attack() {
        weapon.fire();
    }
}
  • weaponは外部から注入される
  • attackの仕組みは自由に差し替え可能
  • weaponのバージョンアップも影響を局所化できる

継承の「ありがちな失敗パターン」から学ぶ

1. 親クラスの想定が破られる(サブクラスが危険な変更)

class BaseService {
    public void run() {
        log();  // 呼び出しを想定している
    }

    protected void log() {
        System.out.println("ログ出力");
    }
}

class AuditService extends BaseService {
    @Override
    protected void log() {
        throw new SecurityException("許可されていません");
    }
}
  • 親はlog()が呼ばれる前提だが…
  • 子がそれを破って例外に変えた → 構造的に破綻

2. “継承したくない機能”まで引き継がれる

class CsvExporter extends ArrayList<String> {
    // これ、remove()とかも持ってる
}
  • 本来「出力専用」のはずが、状態操作まで可能になる
  • 不用意な操作で構造が壊れるリスクがある

3. ライブラリの継承で“縛られる”

class MyController extends SpringController {
    // overrideしなきゃ動かない
}
  • 初期化順序や内部依存に制約
  • 上手く動いているが、設計の自由が失われている

委譲の設計メリットをコードで確認

例:通知処理の分離

class MailSender {
    public void send(String to, String body) {
        // SMTP処理
    }
}

class NotificationService {
    private final MailSender sender;

    public NotificationService(MailSender sender) {
        this.sender = sender;
    }

    public void notify(String userId) {
        // ユーザーIDからメールアドレス取得...
        sender.send("[email protected]", "ようこそ");
    }
}
  • NotificationServiceの中ではsendの実装を知らない
  • テストで MailSender をモックに差し替え可能
  • 責務が分離されていて、影響範囲も明確

UMLで見る委譲構造の明快さ

UML Diagram

継承 or 委譲?──判断の1本軸:変更への耐性

判断ポイント 継承 委譲
実装を柔軟に入れ替えられるか
外部構造に強く依存していないか
テストで差し替え可能か
「使いたい機能だけ」選択できるか
ロジックの副作用範囲が小さいか
レビュー観点

extends を見たら、その継承が「構造上の支配関係」を作っていないかを確認しましょう。

「設計の自由度」を失う継承は、コード量が少なくても将来の技術的負債になります。

結論:継承は最終手段、委譲を第一選択に

  • 委譲は「構造の柔軟性」「変更耐性」「責務の分離」に優れた手段
  • 継承が必要になる場面は、ライブラリのフックや抽象化の極地だけ
  • 現場の8割の構造は、委譲で安全に設計できます