オブジェクト指向 ・ Chapter 12

継承より合成

記事 ・ 冒頭を無料公開中

この章でやること

これまでの仕上げです。ここまで継承を正しく使う方法を学んできましたが、この章では継承の限界を正面から見ます。そして、現代のエンジニアが使うこの原則を手に入れます。

継承より合成(Composition over Inheritance)。

継承が「〜である(is-a)」で作る設計だったのに対して、合成は「〜を持っている(has-a)」で作る設計です。結論から言うと、実務では合成が第一候補です。なぜそうなのかを、継承が壊れていく様子から体験します。

継承が壊れる瞬間: 組み合わせの爆発

ゲームのキャラクターで考えます。キャラクターには武器の種類があり、剣士と魔法使いがいます。継承で素直に作るとこうです。

class Character {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class SwordCharacter extends Character {
  attack() {
    console.log(this.name + "は剣で斬りつけた");
  }
}

class MagicCharacter extends Character {
  attack() {
    console.log(this.name + "は魔法で攻撃した");
  }
}

ここまでは平和です。では、仕様追加です。「剣も魔法も使える魔法剣士を追加したい」。

さっそく困ります。MagicSwordCharacter は SwordCharacter と MagicCharacter のどちらを継承すればいいのでしょう。両方は継承できません(親は1つだけ、でしたね)。仕方なく片方を継承してもう片方の実装をコピペしてみます。はい、継承が防ぐはずだったコピペの復活です。

さらに仕様追加が続きます。「移動手段として徒歩と騎乗を追加。どの武器とも組み合わせ可能」。継承ツリーで表現しようとすると:

SwordWalkCharacter(剣・徒歩)
SwordRideCharacter(剣・騎乗)
MagicWalkCharacter(魔法・徒歩)
MagicRideCharacter(魔法・騎乗)
MagicSwordWalkCharacter(魔法剣・徒歩)
MagicSwordRideCharacter(魔法剣・騎乗)

このチャプターの続きは受講生限定です

本文の残り、図解・実行できるコードブロック・演習はプラン加入後に読めます。

料金プランを見る