组合/聚合复用原则
# 组合/聚合复用原则
合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)一般也叫合成复用原则(Composite Reuse Principle, CRP),定义是:尽量使用合成/聚合,而不是通过继承达到复用的目的。
合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向内部持有的这些对象的委派达到复用已有功能的目的,而不是通过继承来获得已有的功能。
# 聚合(Aggregate)的概念
聚合表示一种弱的"拥有"关系,一般表现为松散的整体和部分的关系,其实,所谓整体和部分也可以是完全不相关的。例如 A 对象持有 B 对象,B 对象并不是 A 对象的一部分,也就是 B 对象的生命周期是 B 对象自身管理,和 A 对象不相关。
# 组合(Composite)的概念
组合表示一种强的"拥有"关系,一般表现为严格的整体和部分的关系,部分和整体的生命周期是一样的。
# 为什么要用组合/聚合来替代继承达到复用的目的
继承复用破坏包装,因为继承将基类的实现细节暴露给派生类,基类的内部细节通常对子类来说是可见的,这种复用也称为"白箱复用"。这里有一个明显的问题是:派生类继承自基类,如果基类的实现发生改变,将会影响到所有派生类的实现;如果从基类继承而来的实现是静态的,不可能在运行时发生改变,不够灵活。
由于组合或聚合关系可以将已有的对象,一般叫成员对象,纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,所以这种复用又称为"黑箱"复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际需要有选择性地调用成员对象的操作;组合/聚合复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。
一般情况下,只有明确知道派生类和基类满IS A
的时候才选用继承,当满足HAS A
或者不能判断的情况下应该选用合成/聚合。
面举个很极端的例子说明一下如果在非IS A
的情况下使用继承会出现什么问题:
先定义一个抽象手,手有一个摇摆的方法,然后定义左右手继承抽象手,实现摇摆方法:
protocol AbstractHand {
func swing()
}
class LeftHand: AbstractHand {
func swing() {
print("Left hand swings...")
}
}
class RightHand: AbstractHand {
func swing() {
print("Right hand swings...")
}
}
现在看起来没有任何问题,实现也十分正确,现在出现了人(Person)这个类,具备摇左右手的功能,如果不考虑IS A
的关系,很有可能有人会这样做:
protocol AbstractHand {
func swing()
}
class SwingHand: AbstractHand {
func swing() {
print(" hand swings...")
}
}
class Person: SwingHand {
func swingLeftHand() {
print("Left ")
swing()
}
func swingRightHand() {
print("Right ")
swing()
}
}
上面 Person 的实现让人觉得百思不得其解,但是往往这会出现在真实的环境中,因为 Hand 不是 Person,所以 Person 继承 SwingHand 一定会出现曲线实现等奇葩逻辑。Hand 和 Person 是严格的部分和整体的关系,或者说 Person 和 Hand 是HAS A
的关系,如果使用组合,逻辑将会十分清晰:
protocol AbstractHand {
func swing()
}
class LeftHand: AbstractHand {
func swing() {
print("Left hand swings...")
}
}
class RightHand: AbstractHand {
func swing() {
print("Right hand swings...")
}
}
class Person {
private let leftHand: AbstractHand
private let rightHand: AbstractHand
init(leftHand: AbstractHand, rightHand: AbstractHand) {
self.leftHand = leftHand
self.rightHand = rightHand
}
func swingLeftHand() {
leftHand.swing()
}
func swingRightHand() {
rightHand.swing()
}
}
let person = Person(leftHand: LeftHand(), rightHand: RightHand())
person.swingLeftHand()
person.swingRightHand()
这里使用了组合,说明了 Person 和 AbstractHand 实例的生命周期是一致的。