コンポジションと継承、「継承を使うな」の本当の意味
オブジェクト指向を学んでいると、必ず次の言葉に出会います。「継承よりコンポジションを優先せよ(Favor composition over inheritance)。」
結論から言うと、これは「継承を絶対に使うな」という意味ではありません。「コードの再利用を目的に継承を乱用するな」という意味です。継承は今でも有効な手段ですが、再利用が必要な場面の多くでは、コンポジションのほうが安全です。
この記事では、実際のコードで両者の違いがどう現れるのか、またいつどちらを選ぶべきかを、私の経験を交えて説明します。
継承とコンポジション、何が違うのか
まずはとてもシンプルに分けて考えてみましょう。
継承は「〜は〜である(is-a)」という関係です。たとえば、犬は動物です。子クラスは親クラスの機能をそのまま受け継ぎます。
コンポジションは「〜は〜を持つ(has-a)」という関係です。車はエンジンを持つ、という具合です。必要な機能を持つオブジェクトを内部に保持し、そのオブジェクトに処理を委譲します。
コードで見ると、よりイメージしやすくなります。以下は継承によって機能を受け継ぐ方法です。
// 継承: Stackは NSMutableArrayのすべてのメソッドを継承
class Stack: NSMutableArray {
func push(_ o: Any) { add(o) }
func pop() -> Any {
let o = lastObject!
removeLastObject()
return o
}
}
この方法では、Stackにとって不要なinsert(_:at:)やremoveObject(at:)のようなメソッドまで外部に公開されてしまいます。
以下は同じものをコンポジションに置き換えた形です。
// コンポジション:必要な機能だけを選んで委譲
class Stack {
private var list: [Any] = []
func push(_ o: Any) { list.append(o) }
func pop() -> Any { list.removeLast() }
}
配列を内部に保持し、必要な操作だけを外部に公開しています。Stackが本当に必要とする機能だけが残ります。
「継承を使うな」の本当の意味
この言葉が生まれた背景には、継承の代表的な弱点が2つあります。
1つ目は、カプセル化が崩れることです。子クラスは親クラスの内部実装に依存します。親のコードが変わると、問題なく動いていた子クラスが突然壊れることもあります。
2つ目は、結合度が高くなりすぎることです。親と子がコンパイル時に強く結び付くため、後から関係を変更するのが難しくなります。
継承はコードを再利用するための道具ではなく、型を定義するための道具です。
この一文が核心です。単にコードを再利用したいという理由で継承を使った瞬間、関係が複雑になり始めます。
有名な例が正方形と長方形です。数学的には正方形は長方形なので、継承すればよさそうに見えます。しかし、長方形の「幅と高さを別々に変更する」機能を受け継いだ瞬間、正方形はもはや正方形ではなくなります。
is-a関係に見えても、振る舞いまで完全に置き換えられないなら、継承は落とし穴になります。
では、継承はいつ使ってよいのでしょうか?
継承が常に悪いわけではありません。次の条件をすべて満たすなら、継承のほうがかえってきれいに書けます。
- 本当のis-a関係か:子クラスは常に親クラスの一種ですか?
- リスコフの置換原則を守っているか:親クラスの代わりに子クラスを置いても問題ありませんか?
- 親クラスは継承を前提に設計されているか:ドキュメントがあり、拡張のために開かれていますか?
3つとも当てはまるなら、継承を使っても問題ありません。UIViewControllerのようなフレームワークベースのクラスを拡張する場合が代表例です。
逆に、1つでも曖昧なら、まずコンポジションを考えてみてください。
表で簡単に比較してみました。
| 状況 | 推奨 |
|---|---|
| 純粋なis-a関係で、完全に置換可能 | 継承 |
| 単にコードを再利用したいとき | コンポジション |
| 実行時に振る舞いを変更したいとき | コンポジション |
| 複数の機能を組み合わせる必要があるとき | コンポジション |
このように、実務で遭遇する状況の多くはコンポジションに傾きます。「favor composition」という助言が生まれたのには理由があります。
実務での私の判断基準
新しいクラスを作るとき、私は習慣的にまず「これは親クラスの一種なのか、それとも親の機能を借りたいだけなのか」と考えます。
借りたいだけなら、ほぼ必ずコンポジションを選びます。
デザインパターンを見ても方向性は明らかです。StrategyパターンやDecoratorパターンなどは、すべてコンポジションを基盤にしています。
特にStrategyパターンは、振る舞いをオブジェクトに分離し、実行時に差し替えます。継承では再現しにくい柔軟性です。
もちろん、コードが少し長くなることは受け入れる必要があります。委譲メソッドを自分で書かなければならないからです。それでも、後から構造を変更するのははるかに簡単です。
「継承を使うな」という言葉は、継承を禁止するのではなく、再利用目的での乱用に注意しようという助言です。is-aが明確なら継承、単に機能を借りたいだけならコンポジション。この1つの基準を意識するだけで、コードはずっと堅牢になります。今日もよい設計を!

