《设计模式之美》之多用组合少用继承

组合、聚合、委托

学习本文前,需要先了解一下组合(compositiion)、聚合(aggregation)、委托(delegation)三者的概念和区别

Association

  • 对象之间有种关系叫做关联关系,英文叫做association
  • 关联关系意味着它们之间有引用关系,比如Managers and Employees之间,Employees是Managers的下属,Managers是Employees的上级,他们之间日常的工作协作就意味着相互引用
  • 关联关系双方有1:1, 1:n, n:n的对应关系

Aggregation

  • 聚合关系是关联关系中的一种
  • 聚合关系中,关联双方的生命周期是独立的,所以是一种比较弱的关联关系

比如Department和Employee的关系

  • Department聚合了Employee,表示部门中有雇员
  • 在实际代码中,Department对象内部可能会持有Employee的引用,但当Department解散后,Employee并不会自动消失

Composition

  • 组合关系也是关联关系的一种,也是聚合关系的一种
  • 组合关系中,只有关联方的生命周期是独立的,被关联方的生命周期是依赖于关联方的。所以是一种比较强的关系

比如上面示例中

  • Company对象组合了一个表示公司位置信息的Complay Location对象

  • 公司创立后边有了公司位置信息,而公司一旦关闭,公司位置信息也就不再存在

  • Association, Aggregation and Composition

Delegation

Delegation又叫做委托

  • 它并不是用来描述对象之间的关系的
  • 而是对行为的描述:表示将某个事情委托/交给另一方去实现的意思
  • 简单讲,A类中有一个方法func doSth(),内部实现则是通过B的实例来实现比如b.doSth(),就可以说A委托B来做事情

继承

继承的优势

两方面

  • 方便代码复用,子类可以轻松复用父类代码
  • 继承天然支持多态
  • 清晰的表示is a的概念,比如鸵鸟Ostrich是鸟Bird的子类,含义清晰,在面向对象设计时很容易想到

继承的劣势

继承层级一旦增多后,代码的可读性和可维护性会急剧降低,比如下面这个图

最初只有一个表示鸟的基类,后面陆续增加可以飞行、不可以飞行、可以叫、不可以等能力的基类

截取一部分代码来看一下这个设计

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)使用了继承关系。

还有一些特殊场景,比如一个我们无法修改源码的外部类,但我们想重写其某个方法的实现,此时只能通过继承

相关推荐
shinelord明11 小时前
【再谈设计模式】命令模式~封装请求的指挥者
设计模式·命令模式
扫地僧00916 小时前
第18章 不可变对象设计模式(Java高并发编程详解:多线程与系统设计)
java·python·设计模式
小王子10241 天前
设计模式Python版 代理模式
python·设计模式·代理模式
sniper_fandc1 天前
详解享元模式
java·设计模式·享元模式
jf加菲猫1 天前
11 享元(Flyweight)模式
c++·设计模式·享元模式
小王子10241 天前
设计模式Python版 享元模式
python·设计模式·享元模式
mofei121381 天前
Python设计模式 - 原型模式
python·设计模式·原型模式
Wendy_robot1 天前
设计模式六大原则和单例模式
单例模式·设计模式
SomeB1oody1 天前
【Rust自学】20.2. 最后的项目:多线程Web服务器
服务器·开发语言·前端·后端·设计模式·rust
fly spider1 天前
wait/notify/join/设计模式
java·开发语言·设计模式