有两个强有力的理由更喜欢组合而不是继承:
假设您正在为比萨店编写一个订购系统。你几乎肯定会有一个比萨课......
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!
}
简而言之,使用继承来处理沿多个轴的多样性会导致类层次结构中的组合爆炸。
第二个问题(运行时修改)也源于此。假设客户查看了他们的 LargeThinCrustMushroomPizza 的价格,呆住了,并决定他们宁愿选择 MediumThinCrustMushroomPizza?现在你被困在制作一个全新的对象只是为了改变那个属性!
这就是组合的用武之地。我们观察到“pepperoni Pizza”确实与 Pizza 具有“is-a”关系,但它也满足与 Pepperoni 的“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 ,客户可以选择更小的尺寸pizza.setSize(new SmallSize())
(getPrice()
这并不是说继承不好。但是,如果可以使用组合而不是继承来表达各种对象(如比萨饼),则通常应该首选组合。