组合优于继承是面向对象编程中的重要原则。而不是在一个单一类中实现所需接口的所有功能,这些功能应该在不同的实例中独立实现,然后使用这些对象最终为目标类提供所有提供的功能。这一原则使代码更具可重用性和可维护性。
示例
让我用一个简单的例子来解释这个想法。假设您在一家汽车公司工作,而您的工作是根据各种要求制造汽车,例如汽车的颜色(外观),最大速度(性能)和座椅数量(内部)。当然,现代汽车还有很多其他的特点,在这个例子中,我们只保留这三个属性。
Java方式
要在Java中实现它,我们首先声明三个接口Appearance
,Performance
和Interior
。
public interface Appearance {
String getColor();
}
public interface Performance {
int getMaxSpeed();
}
public interface Interior {
int getNumberOfSeats();
}
现在您的老板要求您制造一辆黄色的慢速SUV,使用继承就可以了。
public class YellowSlowSUV implements
Appearance,
Performance,
Interior {
@Override
public String getColor() {
return "yellow";
}
@Override
public int getNumberOfSeats() {
return 6;
}
@Override
public int getMaxSpeed() {
return 160;
}
}
如果这是您要制造的唯一一辆汽车,那么上面的代码看起来还可以,但是如果要求您制造另一辆具有相同功能的汽车(除了将颜色更改为红色之外),您将怎么办?
您可以将代码从YellowSlowSUV
类复制并粘贴到新类RedSlowSUV
,然后将getColor()
返回值更改为red?好吧,这很简单,但不是可扩展的解决方案。如果要求您建造另一个GreenSlowSUV
甚至BlackSlowSUV
会怎样?再次以不同的颜色返回值重复整个复制和粘贴过程,这绝对是代码异味的迹象,并且我们不会沿这条路线走。
让我们看看在这种情况下如何应用合成。代替在一个类中实现三个接口,我们可以有三个分别实现这些接口的具体功能类。
// Red.java
public class Red implements Appearance {
@Override
public String getColor() {
return "Red";
}
}
// Slow.java
public class Slow implements Performance {
@Override
public int getMaxSpeed() {
return 160;
}
}
// SixSeat.java
public class SixSeat implements Interior {
@Override
public int getNumberOfSeats() {
return 6;
}
}
并且RedSlowSUV
可以实现如下。
public class RedSlowSUV implements Appearance, Performance, Interior {
private Appearance appearance = new Red();
private Interior interior = new SixSeat();
private Performance performance = new Slow();
@Override
public String getColor() {
return appearance.getColor();
}
@Override
public int getNumberOfSeats() {
return interior.getNumberOfSeats();
}
@Override
public int getMaxSpeed() {
return performance.getMaxSpeed();
}
}
功能的实际实现由Red
,SixSeat
以及Slow
类提供,我们能够根据需求在其他Car
实现中重用这些类。示例GreenSlowSUV,
可以通过利用现有的Slow
和SixSeat
类,而仅增加一个类,来轻松地制造汽车Green
。
public class GreenSlowSUV implements Appearance, Performance, Interior {
private Appearance appearance = new Green();
private Interior interior = new SixSeat();
private Performance performance = new Slow();
@Override
public String getColor() {
return appearance.getColor();
}
@Override
public int getNumberOfSeats() {
return interior.getNumberOfSeats();
}
@Override
public int getMaxSpeed() {
return performance.getMaxSpeed();
}
}
这样,我们就可以利用我们在构建功能块中所付出的辛勤劳动来构建新的Car,并创建类来处理新的业务需求(如果现有的块无法满足这些需求),最终这些新的块也可以重用。
您可能会注意到我们在汽车类中都有的示例代码(getColor(),
getNumberOfSeats()
和getMaxSpeed()
)。我们当然可以创建实现所有这些方法的基类。但这是Java,现在已经是2020年了。让我们来看看如何使用Kotlin对其进行改进。
Kotlin方式
在继续进行操作之前,我们需要了解两个重要的Kotlin关键字,object
以及by
。该object
关键字指示kotlin
编译器创建一个准确的已声明类的实例。对于by
关键字,我们将其与lazy
函数一起主要用于延迟加载示例中。我们也可以使用by
关键字将接口的实现委派给另一个对象,这种情况很少(至少对我而言)。让我们来看一些实际的代码。
首先,我们将功能类声明为object
并实现它们。
object Red: Appearance {
override fun getColor(): String {
return "Red"
}
}
object SixSeat: Interior {
override fun getNumberOfSeats(): Int {
return 6
}
}
object Slow: Performance {
override fun getMaxSpeed(): Int {
return 160;
}
}
然后我们可以只用一行代码来创建我们的Car
class RedSlowSUV: Appearance by Red,
Interior by SixSeat, Performance by Slow
由于所需的接口实现是由对象通过by
关键字委托的,因此我们无需明确地声明已由对象委托的那些方法,因为Kotlin编译器足够的智能,可以为我们生成所需的代码。让我们启动Kotlin字节码查看器并查看它们。
/// Decompiled code shown in Kotlin byte code viewer
public final class RedSlowSUV implements Appearance, Interior, Performance {
// $FF: synthetic field
private final Red $$delegate_0;
// $FF: synthetic field
private final SixSeat $$delegate_1;
// $FF: synthetic field
private final Slow $$delegate_2;
public RedSlowSUV() {
this.$$delegate_0 = Red.INSTANCE;
this.$$delegate_1 = SixSeat.INSTANCE;
this.$$delegate_2 = Slow.INSTANCE;
}
public String getColor() {
return this.$$delegate_0.getColor();
}
public int getNumberOfSeats() {
return this.$$delegate_1.getNumberOfSeats();
}
public int getMaxSpeed() {
return this.$$delegate_2.getMaxSpeed();
}
}
如上所示,生成的Java字节代码看起来与我们在前面的Java实现示例中编写的代码非常相似。因此,基本上Kotlin可以帮助我们编写所有的这些样板代码。
摘要
在本文中,我们通过Java实现中的一个简单示例重新介绍了“组合优于继承”的原则。我们还看到了Kotlin在没有所有样板代码的情况下以更具表现力和简洁的方式实现相同目标的方法。最后,我们检查了Kotlin编译器生成的Java字节代码,并了解了Kotlin在底层进行的实际繁重工作。
这只是Kotlin可以使Java / Android开发人员受益的冰山一角,我们可以从Kotlin那里获得很多帮助我们编写更好代码的东西,我将在以后的文章中介绍它们。