定义
在软件设计过程中,类间关系优先使用组合关系或聚合关系,其次使用继承关系。
对于类的复用,分为合成复用和继承复用,两者都为适应需求变化,对系统进行扩展。合成复用原则强调合成关系的重要性,但并不否定继承关系。只是为达到低耦合系统的目标出发,合成带来的优势比继承更大。两者的优缺点对比如下。
继承复用主要存在以下优缺点:
- 优点
- 简单易实现
- 缺点
- 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
- 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
- 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
合成复用的优缺点:
- 优点
- 将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能。
- 维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
- 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口
- 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
- 缺点
- 需要许多中间类或接口,存在类爆炸风险,降低系统的可读性和可维护性。
作用
- 合成复用原则的名称包含了该原则的主要作用,即达到复用的目的。
- 开闭原则的重要实现方式之一。里氏替换原则利用继承实现开闭原则,合成复用原则利用合成实现开闭原则,两者都提高系统的可扩展性。
- 降低类间的耦合,提高类的独立性。
实现方法
合成复用原则的名称包含了该原则的实现方法,即利用合成达到复用,合成包括组合和聚合。
代码实践
在设计类间关系时,首先要分析业务,判断使用继承关系是否合适。确定可以使用之后,要严格遵循里氏替换原则。
以下代码是采用继承关系实现功能,每当出现新的业务,就需要修改父类内容,违反了开闭原则。
//父类
public class FatherCourse {
public String printJavaMenu(){
return "java课程目录";
}
public String printPythonMenu(){
return "python课程目录";
}
}
//子类
public class Tyler extends FatherCourse{
public void getQueryMenu(){
System.out.println("获取java" + super.printJavaMenu());
}
}
//应用客户端
public class Test {
public static void main(String[] args) {
Tyler g = new Tyler();
g.getQueryMenu();
}
}
采用组合方式实现上述功能,其类图产生的效果类似依赖倒置原则的中例子,依赖发生反转。
//接口
public interface ICourse {
void printMenu();
}
//接口实现类1
public class JavaCourse implements ICourse {
@Override
public void printMenu() {
System.out.println("tyler在学习java课程");
}
}
//接口实现类2
public class PythonCourse implements ICourse{
@Override
public void printMenu() {
System.out.println("tyler在学习python课程");
}
}
//使用接口的中间类
public class Tyler {
public void getQueryMenu(ICourse iCourse){
iCourse.printMenu();
}
}
//应用客户端
public class Test {
public static void main(String[] args) {
Tyler tyler = new Tyler();
tyler.getQueryMenu(new JavaCourse());
tyler.getQueryMenu(new PythonCourse());
}
}
相关知识点
-
类间关系
英文含义的逻辑方向跟uml中类图的箭头的指向一致,譬如 A is-a B,对应的UML图是 A—>B(此处应是三角箭头),即由A指向B。
- 继承 is-a
- 组合 contains-a
- 聚合 has-a
- 依赖 use-a