0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 4-18:継承よりもコンポジションを選ぶ

Posted at

4-18:継承よりもコンポジションを選ぶ

要点

継承(extends)は「is-a」関係/多態性のために使い、再利用のためのコード共有はできるだけコンポジション(委譲)で行う。つまり「継承よりもコンポジションを選ぶ」が安全で柔軟な設計の基本。

なぜコンポジションを優先するのか

  • カプセル化を壊さない: 継承はスーパークラスの実装詳細(protected メンバなど)に依存しがちで、内部実装変更でサブクラスが壊れる。
  • 柔軟性が高い: ランタイムに部品(delegate)を差し替えられる(Strategy / Decorator 等)。
  • API 決定を先延ばしできる: 公開 API を増やさずに内部実装を入れ替えられる。
  • テストしやすい: 内部の委譲オブジェクトをモックに差し替えられる。
  • 継承の誤用を防ぐ: 継承はサブクラスに多くの責任を課す。単にコードを使いたいだけなら委譲の方が安全。

悪い例

public class Stack<E> extends Vector<E> {
    public E push(E item) { addElement(item); return item; }
    public synchronized E pop() {
        if (isEmpty()) throw new EmptyStackException();
        return remove(size() - 1);
    }
}

問題点:

  • Vector の公開メソッド(例えば remove(int) など)によりクライアントが Stack の状態を直接壊せる(LIFO を保証できない)。
  • Stack は「is-a Vector」に見えるが、意味的には「スタック(LIFO)を使っている(has-a)」のほうが自然。

良い例

public class Stack<E> {
    private final List<E> elements = new ArrayList<>();

    public void push(E e) { elements.add(e); }
    public E pop() {
        if (elements.isEmpty()) throw new EmptyStackException();
        return elements.remove(elements.size() - 1);
    }
    public boolean isEmpty() { return elements.isEmpty(); }
}

利点:

  • Stack の公開 API を自分でコントロールできる(不要なメソッドを露出しない)。
  • 内部実装(ArrayList→LinkedList)は将来好きに変えられる。
  • テスト時に elements を差し替えたい場合はコンストラクタ注入を使えば容易。

よく使われるコンポジションパターン

  • デコレータ(Decorator): 既存オブジェクトに機能を付加(BufferedInputStream が InputStream をラップ)。
  • ストラテジー(Strategy): 振る舞いをオブジェクトとして差し替え(ソートアルゴリズム等)。
  • フォワーディング(Forwarding): 内部オブジェクトにメソッドを委譲して必要な API のみ公開。
  • ファサード/アダプタ: 複数オブジェクトをまとめて単純なインタフェースを提供。
// forwarding の例(簡略)
public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;
    public ForwardingSet(Set<E> s) { this.s = s; }
    public boolean add(E e) { return s.add(e); }
    public boolean remove(Object o) { return s.remove(o); }
    // ... 他メソッドを必要に応じて委譲
}

まとめ

継承は強力だが脆い。まずはコンポジション(委譲)で再利用・拡張を行い、本当にサブタイプ化すべきときだけ継承を使う。 これによりカプセル化と柔軟性を保ち、将来の変更コストを下げられる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?