从技术上讲,所有可以通过继承实现的功能也可以通过委托实现。因此答案是“否”。
将继承转换为委托
假设我们使用继承实现了以下类:
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"
。
现在,如果我们使用委托重写它,我们得到:
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开始方法分派。我们实现了与继承相同的效果。这是
prototype-based语言(如
Self)中使用的机制,其中委托具有内置功能(您可以在
here中阅读Self中继承的工作原理)。
但Java没有纯委托。那么我们就卡住了吗?不完全是,我们仍然可以通过更多的努力自己实现。
public class A implements AInterface {
String a = "A";
AInterface owner;
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() ; ... }
}
我们基本上是重新实现了内置继承提供的功能。这有意义吗?不一定。但它说明了继承总是可以转换为委托。
讨论
继承的特点在于基类可以调用子类中被覆盖的方法。这是
模板模式的本质。这些事情无法轻松地使用委托来完成。另一方面,这正是使继承难以使用的原因。需要进行心理转变才能理解多态分派发生的位置以及方法被覆盖时的影响。
关于继承和它可能引入的设计
脆弱性,有一些已知的陷阱。特别是如果类层次结构发生变化。如果使用继承,还可能存在
相等性在
hashCode
和
equals
中的问题。但另一方面,它仍然是解决某些问题的非常优雅的方式。
此外,即使继承可以用委托替代,你可以争辩说它们仍然实现了不同的目的并相互补充 -- 它们没有传达相同的
意图,这不是纯粹的
技术等价所能捕捉到的。
我的理论是,当有人开始使用面向对象编程时,我们很容易过度使用继承,因为它被认为是该语言的一个特性。然后我们学习委托这种模式/方法,也开始喜欢它。经过一段时间,我们找到了两者之间的平衡,并发展出一种直觉,知道在哪种情况下哪种更好。嗯,正如你所看到的,我仍然喜欢两者,并且在引入它们之前都需要谨慎。
一些文献
-
委托就是继承
继承和委托是增量定义和共享的替代方法。通常认为委托提供了更强大的模型。本文证明了继承有一个“自然”的模型,可以捕捉所有委托的属性。此外,还证明了委托无法捕获继承的某些约束条件。最后,概述了一个完全捕获委托和继承的新框架,并探讨了这种混合模型的一些影响。
-
关于继承的概念
面向对象编程中最有趣且最棘手的概念之一是继承。继承通常被认为是区别于其他现代编程范式的特征,但研究人员很少就其含义和用法达成共识。[...]
由于类之间的强耦合和继承所引发的不必要类成员的增加,使用组合和委托的建议已经变得司空见惯。文献中对应的重构演示可能会让人误以为这样的转换是一个简单的任务。[...]