継承よりも構成を優先する 2 つの強い理由があります。
- クラス階層での組み合わせの爆発を回避します。
- 実行時に変更可能
ピザ屋の注文システムを書いているとしましょう。あなたはほぼ確実にクラスのピザを持っているでしょう...
public class Pizza {
public double getPrice() { return BASE_PIZZA_PRICE; }
}
そして、他のすべてが同じであれば、ピザパーラーはおそらくペパロニピザをたくさん売っています. これには継承を使用できます。PepperoniPizza はピザとの「is-a」関係を満たしているため、有効に聞こえます。
public class PepperoniPizza extends Pizza {
public double getPrice() { return super.getPrice() + PEPPERONI_PRICE; }
}
よし、ここまではいいぞ?しかし、私たちが考慮していないことがわかるでしょう。たとえば、顧客がペパロニとマッシュルームを欲しがっていたらどうしますか? さて、PepperoniMushroomPizza クラスを追加できます。すでに問題があります。PepperoniMushroomPizza は、Pizza、PepperoniPizza、または MushroomPizza を拡張する必要がありますか?
しかし、事態はさらに悪化します。架空のピザ パーラーが、小、中、大のサイズを提供しているとします。また、クラストもさまざまです。厚い、薄い、通常のクラストがあります。継承だけを使用している場合、突然、MediumThickCrustPepperoniPizza、LargeThinCrustMushroomPizza、SmallRegularCrustPepperoniAndMushroomPizza などのクラスが作成されます...
public class LargeThinCrustMushroomPizza extends ThinCrustMushroomPizza {
// This is not good!
}
つまり、継承を使用して複数の軸に沿った多様性を処理すると、クラス階層で組み合わせ爆発が発生します。
2 番目の問題 (実行時の変更) もこれに由来します。顧客が LargeThinCrustMushroomPizza の価格を見て、gawks を見て、代わりに MediumThinCrustMushroomPizza を購入したいと判断したとします。これで、その 1 つの属性を変更するためだけに、まったく新しいオブジェクトを作成することになりました。
これが構成の出番です。「ペパロニ ピザ」は実際にピザと「is-a」の関係を持っていますが、ペパロニとの「has-a」の関係も満たしていることがわかります。また、地殻の種類とサイズとの「has-a」関係も満たします。したがって、コンポジションを使用して Pizza を再定義します。
public class Pizza {
private List<Topping> toppings;
private Crust crust;
private Size size;
//...omitting constructor, getters, setters for brevity...
public double getPrice() {
double price = size.getPrice();
for (Topping topping : toppings) {
price += topping.getPriceAtSize(size);
}
return price;
}
}
この構成ベースのピザでは、顧客はより小さいサイズ ( pizza.setSize(new SmallSize())
) を選択でき、価格 ( getPrice()
) は適切に応答します。つまり、メソッドの実行時の動作は、オブジェクトの実行時の構成によって異なる場合があります。
継承が悪いと言っているわけではありません。しかし、継承の代わりにコンポジションを使用してさまざまなオブジェクト (ピザなど) を表現できる場合は、通常、コンポジションを優先する必要があります。