关于oop:有什么组合无法实现继承可以吗?

Is there anything composition cannot accomplish that inheritance can?

组成和继承。

我知道它们都是适当时要选择的工具,上下文在组合和继承之间的选择非常重要。然而,关于每种方法的适当上下文的讨论通常有点模糊;这让我开始考虑传统OOP中继承和多态是多么的明显。

多态性允许我们同样指定"IS-A"关系和继承关系。尤其是,从基类继承隐式地在该类及其子类之间创建了多态关系。然而,多态性可以使用纯接口实现,而继承通过同时传输实现细节使多态关系复杂化。通过这种方式,继承与纯多态性是截然不同的。

作为一种工具,继承通过简化简单情况下的实现重用来为程序员提供不同于多态(通过纯接口)的服务。然而,在大多数情况下,超类的实现细节微妙地与子类的需求冲突。这就是为什么我们有"覆盖"和"成员隐藏"的原因。在这些情况下,继承提供的实现重用是通过在代码的级联级别上验证状态更改和执行路径来购买的:子类的完整"扁平"实现细节分布在多个类之间,这通常意味着多个文件,其中只有部分适用于有问题的子类。在处理继承时,查看这个层次结构是绝对必要的,因为如果不查看超类的代码,就无法知道哪些不可忽略的细节正在干扰您的状态或转移您的执行。

相比之下,排他性地使用组合保证您将看到哪些状态可以通过显式实例化的对象进行修改,这些对象的方法是由您自己决定调用的。真正扁平化的实现仍然没有实现(实际上甚至不可取,因为结构化编程的好处是实现细节的封装和抽象),但是您仍然可以获得代码重用,并且只有在代码行为不当时,您才需要查看一个地方。

为了在实践中测试这些思想,避免传统的继承,将基于接口的多态性和对象组合结合起来,我想知道,

有没有什么对象组合和接口不能完成继承?

编辑

在迄今为止的回应中,Ewernli认为一种技术没有技术专长,但另一种技术没有;他后来提到了每种技术固有的不同模式和设计方法。这是有道理的。然而,这个建议让我通过询问是否将组合和接口的专用性代替传统的继承而禁止使用任何主要的设计模式来完善我的问题?如果是这样的话,在我的情况下,是否有等价的模式可供使用?


从技术上讲,继承可以实现的一切也可以通过委托来实现。所以答案是"不"。

继承转委托

假设我们使用继承实现了以下类:

1
2
3
4
5
6
7
8
9
10
11
12
public class A {
    String a ="A";
    void doSomething() { .... }
    void getDisplayName() {  return a }
    void printName { System.out.println( this.getDisplayName() };  
}

public class B extends A {
    String b ="B";
    void getDisplayName() {  return a +"" + b; }
    void doSomething() { super.doSomething() ; ... }    
}

这些东西工作得很好,在B的一个实例上调用printName将在控制台中打印"A B"

现在,如果我们用委托书重写它,我们得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class A {
    String a ="A";
    void doSomething() { .... }
    void getDisplayName() {  return a }
    void printName { System.out.println( this.getName() };  
}

public class B  {
    String b ="B";
    A delegate = new A();
    void getDisplayName() {  return delegate.a +"" + b; }
    void doSomething() { delegate.doSomething() ; ... }
    void printName() { delegate.printName() ; ... }
}

我们需要在B中定义printName,并在B实例化时创建委托。对doSomething的调用将以与继承类似的方式工作。但是调用printName会在控制台中打印"A"。实际上,通过委托,我们失去了将"this"绑定到对象实例的强大概念,并且基方法能够调用已被重写的方法。

这可以用支持纯委托的语言来解决。在纯委托的情况下,委托中的"this"仍然引用B的实例,这意味着this.getName()将从B类开始方法调度,我们实现的方法与继承相同。这是在基于原型的语言(如self)中使用的机制,self具有委托的内置功能(您可以在这里阅读继承如何在self中工作)。

但是Java没有纯粹的授权。什么时候卡住了?不,真的,我们还是可以自己做,再努力一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class A implements AInterface {
    String a ="A";
    AInterface owner; // replace"this"
    A ( AInterface o ) { owner = o }
    void doSomething() { .... }
    void getDisplayName() {  return a }
    void printName { System.out.println( owner.getName() };
}

public class B  implements AInterface {
    String b ="B";
    A delegate = new A( this );
    void getDisplayName() {  return delegate.a +"" + b; }
    void doSomething() { delegate.doSomething() ; ... }
    void printName() { delegate.printName() ; ... }
}

我们基本上正在重新实现内置继承提供的功能。这有道理吗?没有。但它说明了继承总是可以转换为委托。

讨论

继承的特点是基类可以调用在子类中重写的方法。这就是模板模式的本质。这样的事情不能轻易地通过授权来完成。另一方面,这正是继承难以使用的原因。它需要一个心理扭曲来理解多态分派在哪里发生,以及如果方法被重写会产生什么效果。

对于继承和它在设计中可能引入的脆弱性,有一些已知的陷阱。尤其是在类层次结构发展的情况下。如果使用继承,hashCodeequals中也可能存在一些平等问题。但另一方面,它仍然是解决一些问题的一种非常优雅的方法。

此外,即使继承可以用委托来代替,您也可以认为它们仍然实现了不同的目的,并且相互补充——它们没有传达出纯粹的技术等价所没有的相同意图。

(我的理论是,当有人开始进行OO时,我们会倾向于过度使用继承,因为它被认为是语言的一个特征。然后我们学习委派,这是模式/方法,我们也学会了喜欢它。经过一段时间,我们发现两者之间的平衡和发展的直觉感,其中一个更好的情况下。好吧,正如你所看到的,我仍然喜欢两者,在被介绍之前,两者都值得注意。)

一些文献

  • 委托是继承

Inheritance and delegation are
alternate methods for incremental
definition and sharing. It has
commonly been believed that delegation
provides a more powerful model. This
paper demonstrates that there is a
"natural" model of inheritance which
captures all of the properties of
delegation. Independently, certain
constraints on the ability of
delegation to capture inheritance are
demonstrated. Finally, a new framework
which fully captures both delegation
and inheritance is outlined, and some
of the ramifications of this hybrid
model are explored.

  • 论继承的概念

One of the most intriguing—and at the
same time most problematic—notions in
object-oriented programing is
inheritance. Inheritance is commonly
regarded as the feature that
distinguishes object-oriented
programming from other modern
programming paradigms, but researchers
rarely agree on its meaning and usage. [...]

  • 在Java中系统地重构继承到委托

Because of the strong coupling of classes and the proliferation of unneeded class members induced
by inheritance, the suggestion to use composition and delegation instead has become commonplace.
The presentation of a corresponding refactoring in the literature may lead one to believe that
such a transformation is a straightforward undertaking. [...]


当快速开发人员试图通过添加方法和扩展层次结构(而不是考虑自然的层次结构)来解决问题时,组合不能像继承一样破坏生命。

合成物不会产生奇怪的钻石,这会导致维修队燃烧夜油,划伤他们的头。

继承是讨论gof设计模式的本质,如果程序员首先使用组合,那么继承模式就不一样了。


我可能错了,但我还是要说,如果有人有我错的原因,请发表评论,不要投我反对票。我能想到的一种情况是,继承优于组合。

假设我在一个项目中使用了一个封闭源代码小部件库(这意味着除了文档之外,实现细节对我来说是一个谜)。现在假设每个小部件都能够添加子小部件。通过继承,我可以扩展widget类来创建一个customwidget,然后将customwidget添加为库中任何其他小部件的子小部件。然后我添加自定义小部件的代码如下所示:

1
2
3
Widget baseWidget = new Widget();
CustomWidget customWidget = new CustomWidget();
baseWidget.addChildWidget(customWidget);

非常干净,并且符合库中添加子窗口小部件的约定。但是,对于构图,它必须是这样的:

1
2
3
Widget baseWidget = new Widget();
CustomWidget customWidget = new CustomWidget();
customWidget.addAsChildToWidget(baseWidget);

不是很干净,也破坏了图书馆的惯例

现在我并不是说你不能用作曲来完成这一点(事实上,我的例子表明你可以很清楚地做到),它只是在所有情况下都不理想,并可能导致打破惯例和其他视觉上不吸引人的解决办法。


考虑一个GUI工具包。

编辑控件是一个窗口,它应该继承窗口的关闭/启用/绘制功能-它不包含窗口。

然后,富文本控件应该包含编辑控件保存/读取/剪切/粘贴功能,如果它只包含一个窗口和一个编辑控件,将很难使用。


对。它是运行时类型标识(RTTI)。