是否有任何组合无法实现继承可以?

Dav*_*Kay 25 oop polymorphism inheritance composition

组成和继承.

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

多态性允许人们同等地指定"is-a"关系以及继承.特别是,从基类继承隐式地创建该类与其子类之间的多态关系.然而,尽管可以使用纯接口实现多态,但是继承通过同时传输实现细节使多态关系复杂化.通过这种方式,继承与纯多态性完全不同.

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

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

我的目标是在实践中测试这些想法,避免传统继承为基于纯接口的多态性和对象组合的组合,我想知道,

有什么对象组成和接口无法实现继承吗?

编辑

在迄今为止的回复中,ewernli认为一种技术没有技术专长,而另一种技术则没有; 后来他提到了每种技术固有的不同模式和设计方法.这是有道理的.但是,这个建议让我通过询问是否专用组合和界面代替传统继承来禁止使用任何主要设计模式来改进我的问题?如果是这样,在我的情况下是不是有相同的模式?

ewe*_*nli 23

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

将继承转换为委托

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

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() ; ... }    
}
Run Code Online (Sandbox Code Playgroud)

这些东西很好用,调用printNameB的实例将"A B"在控制台中打印.

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

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() ; ... }
}
Run Code Online (Sandbox Code Playgroud)

我们需要printName在B中定义,并在实例化B时创建委托.调用doSomething将以与继承类似的方式工作.但是调用printName"A"在控制台中打印.确实有了委托,我们失去了强大的"this"概念,它被绑定到对象实例,而基本方法能够调用已被覆盖的方法.

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

但是Java并没有纯粹的委托.什么时候被卡住了?不,我们仍然可以通过更多的努力来做到这一点:

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() ; ... }
}
Run Code Online (Sandbox Code Playgroud)

我们基本上重新实现内置继承提供的内容.是否有意义?不完全是.但它说明继承总是可以转换为委托.

讨论

继承的特点是基类可以调用在子类中重写的方法.这是模板模式的本质.这种事情不能通过授权轻松完成.另一方面,这正是使继承难以使用的原因.它需要精神上的扭曲来理解多态分派发生的位置以及如果覆盖方法会产生什么影响.

关于遗传以及它可能在设计中引入的脆弱性存在一些已知的缺陷.特别是如果类层次结构发展.也可能有一些问题,平等hashCodeequals如果继承使用.但另一方面,它仍然是解决一些问题的一种非常优雅的方式.

而且,即使继承可以用委托代替,你也可以争辩说它们仍然达到了不同的目的并相互补充 - 它们没有传达出纯粹的技术等同性所没有的相同意图.

(我的理论是,当有人开始做OO时,我们倾向于过度使用继承,因为它被认为是语言的一个特征.然后我们学习授权,即模式/方法,我们也学会喜欢它.经过一段时间,我们在两者之间找到了平衡,并且发展了直觉感,在这种情况下哪一个更好.嗯,正如你所看到的,我仍然喜欢两者,并且在引入之前都值得谨慎.)

一些文献

继承和委派是增量定义和共享的替代方法.人们普遍认为,委托提供了一个更强大的模型.本文证明了一种"自然"的继承模型,它捕获了委托的所有属性.独立地,证明了对委托捕获继承的能力的某些限制.最后,概述了一个完全捕获委托和继承的新框架,并探讨了这种混合模型的一些后果.

面向对象编程中最有趣且同时也是最有问题的概念之一是继承.继承通常被认为是将面向对象编程与其他现代编程范式区分开来的特征,但研究人员很少同意其含义和用法.[...]

由于类的强耦合和由继承引起的不需要的类成员的扩散,使用组合和委托的建议已经变得司空见惯.在文献中提出相应的重构可能会让人相信这种转变是一项直接的任务.[...]

  • 第一段是我的,是我的意见.然后有三篇参考文献的相关论文,我引用了摘要.doi可以在所有指向portal.acm.org的链接中找到.对不起,如果不清楚.如果你找不到它们,请告诉我pdf. (2认同)
  • 有件事迫使我投了反对票。尽管我同意继承经常被滥用,但问题/答案的主旨让我感到困扰。继承是一种专门的工具。仅仅因为有人不知道如何使用它,并不意味着你就扔掉它。人们可能会很容易地问:“有什么 Java 可以做而字节码却做不到的事情吗?”。显然不是,但你自己强调了这个问题:“这有意义吗?没有意义。” (2认同)

que*_*zen 5

当快枪程序员试图通过添加方法和扩展层次结构(而不​​是考虑自然层次结构)来解决问题时,组合不会像继承那样搞砸生活。

成分不能产生奇怪的钻石,这会导致维护团队夜以继日地摸不着头脑

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