组合、聚合、委托
学习本文前,需要先了解一下组合(compositiion)、聚合(aggregation)、委托(delegation)三者的概念和区别
Association
- 对象之间有种关系叫做关联关系,英文叫做association
- 关联关系意味着它们之间有引用关系,比如Managers and Employees之间,Employees是Managers的下属,Managers是Employees的上级,他们之间日常的工作协作就意味着相互引用
- 关联关系双方有1:1, 1:n, n:n的对应关系
Aggregation
- 聚合关系是关联关系中的一种
- 聚合关系中,关联双方的生命周期是独立的,所以是一种比较弱的关联关系
比如Department和Employee的关系
![](https://file.jishuzhan.net/article/1735129994042019842/91899b44d5aa71146518dcdf5073838e.webp)
- Department聚合了Employee,表示部门中有雇员
- 在实际代码中,Department对象内部可能会持有Employee的引用,但当Department解散后,Employee并不会自动消失
Composition
- 组合关系也是关联关系的一种,也是聚合关系的一种
- 组合关系中,只有关联方的生命周期是独立的,被关联方的生命周期是依赖于关联方的。所以是一种比较强的关系
![](https://file.jishuzhan.net/article/1735129994042019842/0b0e7afcda70fc4c2086c704795fa8b4.webp)
比如上面示例中
-
Company对象组合了一个表示公司位置信息的Complay Location对象
-
公司创立后边有了公司位置信息,而公司一旦关闭,公司位置信息也就不再存在
Delegation
Delegation又叫做委托
- 它并不是用来描述对象之间的关系的
- 而是对行为的描述:表示将某个事情委托/交给另一方去实现的意思
- 简单讲,A类中有一个方法
func doSth()
,内部实现则是通过B的实例来实现比如b.doSth()
,就可以说A委托B来做事情
继承
继承的优势
两方面
- 方便代码复用,子类可以轻松复用父类代码
- 继承天然支持多态
- 清晰的表示
is a
的概念,比如鸵鸟Ostrich
是鸟Bird
的子类,含义清晰,在面向对象设计时很容易想到
继承的劣势
继承层级一旦增多后,代码的可读性和可维护性会急剧降低,比如下面这个图
![](https://file.jishuzhan.net/article/1735129994042019842/5940984e7d6128fe1505472401167a34.webp)
最初只有一个表示鸟的基类,后面陆续增加可以飞行、不可以飞行、可以叫、不可以等能力的基类
截取一部分代码来看一下这个设计
scala
public class AbstractBird {
//...省略其他属性和方法...
public void fly() { //... }
}
public class Ostrich extends AbstractBird { //鸵鸟
//...省略其他属性和方法...
public void fly() {
throw new UnSupportedMethodException("I can't fly.'");
}
}
- 可想而知,如果要排查代码,这个类层级以及基类个数,可读性肯定不够好
- 同时基类的改动会影响到所有子类,复杂的继承关系更会加剧降低代码可维护性
继承的所有优势,都可以用非继承手段实现
继承的所有优势,可以用组合、接口、委托等手段来实现
以上面鸟的例子来说
- 多态可以用接口/协议来实现
- 代码复用可以用组合和委托来实现
java
public interface Flyable {
void fly();
}
public class FlyAbility implements Flyable {
@Override
public void fly() { //... }
}
//省略Tweetable/TweetAbility/EggLayable/EggLayAbility
public class Ostrich implements Tweetable, EggLayable {//鸵鸟
private TweetAbility tweetAbility = new TweetAbility(); //组合
private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
//... 省略其他属性和方法...
@Override
public void tweet() {
tweetAbility.tweet(); // 委托
}
@Override
public void layEgg() {
eggLayAbility.layEgg(); // 委托
}
}
当然,is a
这个描述确实因此丢失了,但可以用组合的has a
关系来代替
什么情况下可以考虑继承
如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。反之,系统越不稳定,继承层次很深,继承关系复杂,我们就尽量使用组合来替代继承。
除此之外,还有一些设计模式会固定使用继承或者组合。比如,装饰者模式(decorator pattern)、策略模式(strategy pattern)、组合模式(composite pattern)等都使用了组合关系,而模板模式(template pattern)使用了继承关系。
还有一些特殊场景,比如一个我们无法修改源码的外部类,但我们想重写其某个方法的实现,此时只能通过继承