我希望能够对如何正确实现“组合优于继承”进行一些澄清。我认为我已经掌握了理论,但在实践中我很难理解它如何不会导致代码重复。
我有以下示例(它是Java):假设我们有一个抽象动物类:
abstract class Animal {
protected void eat() {
// Common eating implementation
}
protected void sleep() {
// Common sleeping implementation
}
}
我们想要建造飞行和游泳的动物。我知道,遵循 LSP 的最佳方法是为每个接口提供接口:
interface Flyer {
void fly();
}
interface Swimmer {
void swim();
}
那么我们就会有
class Salmon extends Animal implements Swimmer {
@Override
public void swim() {
// Swim implementation
}
}
class Sparrow extends Animal implements Flyer {
@Override
public void fly() {
// Fly implementation
}
}
但是随后我们得到了一个新的要求,要求
Magpie
的飞行方式与麻雀相同。我们将创建一个类,与 Sparrow
: 没有太大不同
class Magpie implements Animal implements Flyer {
@Override
public void fly() {
// Same exact fly implementation as Sparrow.fly
}
}
为了本练习的目的,想象一下,苍蝇的实现非常复杂,包括数据库集成、日志记录等——这会导致大量重复的代码,如果我们添加更多的鸟或鱼,我们就需要重复代码甚至更进一步。
还有一个想法是为相同类型的传单创建一个抽象类,例如
abstract class FlyingBird extends Animal implements Flyer
并且在那里有通用的实现,但是如果我们需要创建其中一些并且有一个动物需要扩展其中两个怎么办?这是一个滑坡...
有什么办法可以避免这种情况吗?还是我在某个地方遗漏了标记?
在组合中,由于多种原因,我更喜欢组合而不是继承来共享代码,您可以通过注入通用实现来共享代码。
在您的示例中,您可以有一个可以在 Sparrow 和 Magpie 之间共享的 Flyer impl。命名它可能有点困难 - 我建议使用“先使用后重用”原则,因此只需给它一个足够好的名称,并在需要时进行重构。
例如,您可以有一个 SmallBirdFlyer 实现,将其注入到 Sparrow 和 Magpie 实现中,然后使用 Sparrow 和 Magpie 对该实现的委托。或者,您可以让他们通过方法(例如
getFlyer()
)返回 Flyer,而不是让 Sparrow 和 Magpie 直接实现 Flyer,然后 Sparrow 和 Magpie 可以直接在那里返回 SmallBirdFlyer impl。
这是使用委托的示例:
class Magpie implements Animal, Flyer {
private SmallBirdFlyer myFlyer;
public void setMyFlyer(SmallBirdFlyer myFlyer) {
this.myFlyer = myFlyer;
}
@Override
public void fly() {
this.myFlyer.fly();
}
}
我是这样想的 - 不是从顶层下推功能,然后寻找在较低级别进行定制的方法,而是构建通用功能以供在较高级别使用。