首选构图不仅与多态有关。尽管这是其中的一部分,但您是正确的(至少使用名义上的语言),人们真正的意思是“更喜欢组合和接口实现的组合”。但是,(在许多情况下)更喜欢构图的原因是深刻的。
多态性是一件事,表现出多种方式。因此,泛型/模板是“多态”功能,因为它们允许单个代码随类型改变其行为。实际上,这种类型的多态性实际上表现得最好,因为变异是由参数定义的,因此通常被称为参数多态性。
许多语言提供了一种称为“重载”或即席多态的多态形式,其中以同名方式定义了多个具有相同名称的过程,并且由该语言选择了一个过程(也许是最具体的)。这是表现最差的一种多态性,因为除了已开发的约定外,没有其他任何东西可以连接这两个过程的行为。
第三种多态性是亚型多态性。在此,在给定类型上定义的过程也可以在该类型的“子类型”的整个族中使用。当实现接口或扩展类时,通常是在声明要创建子类型的意图。真正的子类型受Liskov的替代原理支配,也就是说,如果您可以证明有关超类型中所有对象的信息,则可以证明有关子类型中所有实例的信息。但是,生命变得危险,因为在诸如C ++和Java之类的语言中,人们通常对类的假设没有强制性,而且常常是没有记录的假设,而关于子类的假设可能不正确。也就是说,编写代码时似乎可以证明的确实比实际更多,这会在您不小心键入子类型时产生大量问题。
继承实际上与多态无关。给定事物“ T”对其自身的引用,当您从“ T”创建一个新事物“ S”时,就会发生继承,而用“ S”的引用替换“ T”对自身的引用。该定义是故意含糊的,因为继承可以在许多情况下发生,但是最常见的是将对象子类化,从而具有将this
虚拟函数调用的指针替换为指向this
子类型的指针的作用。
就像所有非常强大的事物一样,继承是危险的,继承具有造成破坏的能力。例如,假设您从某个类继承时重写了一个方法:一切都很好,直到该类的其他方法假定您继承的方法以某种方式表现出来,这毕竟是原始类的作者设计它的方式。您可以通过声明由另一个方法调用的所有方法为私有或非虚拟(最终)方法来部分地防止此情况发生,除非它们被设计为被覆盖。即使这并不总是足够好。有时您可能会看到类似的信息(在伪Java中,希望对C ++和C#用户可读)
interface UsefulThingsInterface {
void doThings();
void doMoreThings();
}
...
class WayOfDoingUsefulThings implements UsefulThingsInterface{
private foo stuff;
public final int getStuff();
void doThings(){
//modifies stuff, such that ...
...
}
...
void doMoreThings(){
//ignores stuff
...
}
}
您认为这很可爱,并且拥有自己的“事物”处理方式,但是您可以通过继承来获得“更多事物”的处理能力,
class MyUsefulThings extends WayOfDoingUsefulThings{
void doThings {
//my way
}
}
一切都很好。WayOfDoingUsefulThings
的设计方式是,替换一个方法不会改变任何其他方法的语义...除了等待,不,不是。看起来就像是,但是doThings
重要的是更改了可变状态。因此,即使它没有调用任何可覆盖的函数,
void dealWithStuff(WayOfDoingUsefulThings bar){
bar.doThings()
use(bar.getStuff());
}
现在通过时所做的事情与预期不同MyUsefulThings
。更糟糕的是,您甚至可能不知道WayOfDoingUsefulThings
做出了这些承诺。也许dealWithStuff
来自同一库WayOfDoingUsefulThings
和getStuff()
甚至没有通过库输出(想想友元类在C ++中)。更糟的是,你已经打败了语言的静态检查没有意识到这一点:dealWithStuff
花了WayOfDoingUsefulThings
只是为了确保它会具有getStuff()
其行为以某种方式功能。
使用构图
class MyUsefulThings implements UsefulThingsInterface{
private way = new WayOfDoingUsefulThings()
void doThings() {
//my way
}
void doMoreThings() {
this.way.doMoreThings();
}
}
带回静态类型的安全性。通常,在实现子类型化时,组合比继承更易于使用且更安全。它还使您可以覆盖final方法,这意味着除了绝大多数时候在接口中之外,您都可以随意声明所有 final / non-virtual方法。
在更好的世界中,语言会自动插入带有delegation
关键字的样板。大多数都不是,所以缺点是更大的类。虽然,您可以让您的IDE为您编写委派实例。
现在,生活不仅仅是多态。您不需要一直都是子类型。多态性的目标通常是代码重用,但这并不是实现该目标的唯一方法。通常,在没有子类型多态的情况下使用组合作为管理功能的方式是有意义的。
而且,行为继承确实有其用途。它是计算机科学中最强大的思想之一。就是这样,在大多数情况下,仅可以使用接口继承和组合来编写好的OOP应用程序。两项原则
- 禁止继承或为其设计
- 偏好组成
出于上述原因,是很好的指南,并且不会产生任何实质性费用。