组合优于继承的探讨

组合优于继承的探讨

技术背景

在面向对象编程中,继承和组合是两种重要的代码复用和设计方式。继承允许子类继承父类的属性和方法,形成“is-a”关系;组合则是通过在一个类中包含另一个类的对象,形成“has-a”关系。长期以来,“组合优于继承”的观点被广泛提及,理解两者的区别和适用场景对于编写高质量的代码至关重要。

实现步骤

继承的实现

继承通过定义子类来继承父类的特性。以下是一个简单的 Java 示例:

1
2
3
4
5
6
7
8
9
10
class Person {
String Title;
String Name;
int Age;
}

class Employee extends Person {
int Salary;
String Title;
}
JAVA

在这个例子中,Employee 类继承自 Person 类,Employee “is a” Person

组合的实现

组合是将其他类的对象作为成员变量。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
String Title;
String Name;
int Age;

public Person(String title, String name, int age) {
this.Title = title;
this.Name = name;
this.Age = age;
}
}

class Employee {
int Salary;
private Person person;

public Employee(Person p, int salary) {
this.person = p;
this.Salary = salary;
}
}
JAVA

这里,Employee 类包含一个 Person 对象,Employee “has a” Person

核心代码

继承示例

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
void move() {
System.out.println("Animal is moving");
}
}

class Dog extends Animal {
@Override
void move() {
System.out.println("Dog is running");
}
}
JAVA

组合示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Moveable {
void move();
}

class AnimalMove implements Moveable {
@Override
public void move() {
System.out.println("Animal is moving");
}
}

class Dog {
private Moveable moveable;

public Dog(Moveable moveable) {
this.moveable = moveable;
}

void performMove() {
moveable.move();
}
}
JAVA

最佳实践

优先使用组合

当代码复用是唯一目的,且父类的部分公共方法对子类没有意义时,应优先考虑组合。例如,一个 Stack 类不应该继承 List 类,因为 Stack 只需要 poppushpeek 等功能,而不需要 List 的其他方法。

谨慎使用继承

  • 当子类和父类之间存在明确的“is-a”关系,且子类可以完全替代父类时,可使用继承。例如,Dog 类可以继承 Animal 类,因为狗是动物,且狗可以做动物能做的一切事情。
  • 如果需要实现多态,可使用继承,但要遵循 Liskov 替换原则。

遵循设计原则

遵循单一职责原则,每个类只负责一项职责。使用接口和策略模式来提供可定制的行为,避免过度使用继承。

常见问题

继承的问题

  • 难以维护:修改父类可能会影响到所有子类,导致代码难以维护。
  • 违反封装原则:父类的实现细节暴露给子类,破坏了封装性。
  • 菱形继承问题:在多继承中可能会出现菱形继承问题,导致代码逻辑混乱。

组合的问题

  • 代码冗余:有时需要编写额外的代码来委托方法调用,可能会违反 DRY 原则。
  • 性能问题:方法调用链过长可能会影响性能。

组合优于继承的探讨
https://119291.xyz/posts/composition-over-inheritance-discussion/
作者
ww
发布于
2025年5月23日
许可协议