Skip to main content

1.3 合成复用原则

定义

尽量使用组合而不是继承

补充说明

“继承”属于面向对象编程的概念,函数式编程中没有“继承”的概念。但是,函数式编程其实也可以实现面向对象编程中的“继承”,只是把继承的对象从 “类”换成“模块”即可。如Rescript作为一个函数式编程语言,支持在一个模块B中引入另一个模块A,使B获得A所有的成员,这就相当于实现了B继承A。相关代码如下:

module A = {
let value1 = "A1"

let func1 = () => {
//返回1
1
}
}

module B = {
//引入A,获得了A.value1, A.func1
include A

//覆盖了A.value1
let value1 = "B1"

let func2 = () => {
//调用A.func1并返回
func1()
}
}

继承在函数式编程中的领域模型如下:

继承的领域模型图

我们来看下继承的优点:

  • 因为子模块可以直接通过继承获得父模块的成员,所以实现新的子模块比较容易

继承的缺点:

  • 因为继承破坏了封装,父模块的细节完全暴露给子模块,所以父模块的任何细节发生了改变都会影响到子模块
  • 因为继承是静态的,所以在运行时不能改变继承关系

组合在函数式编程中的领域模型如下:

组合的领域模型图

我们来看下组合的优点:

  • 因为新模块看不到已有模块的细节,新模块只能通过已有模块的接口来访问它,所以组合维持了封装
  • 因为组合是动态的,所以在运行时能改变组合关系

组合的缺点:

  • 模块的数量会比较多

继承关系属于“Is-A”的关系,组合关系属于“Has-A”的关系

案例1

案例1的领域模型图

如上图所示,因为雇员、经理都是人,它们与人是“Is-A”的关系,所以它们的关系是继承关系。实际上,雇员、经理是两种角色,而人可以同时有几种不同的角色,如一个人可以既是雇员又是经理。然而这里因为使用了继承,所以一个人就只能是雇员或者经理。因此,我们将继承关系改为组合关系,修改后的领域模型如下:

案例1重构后的领域模型图

现在,因为人与角色是“Has-A”的关系,所以一个人可以通过组合不同的角色,来实现一个人既是雇员又是经理