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

组合、聚合、委托

学习本文前,需要先了解一下组合(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)使用了继承关系。

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

相关推荐
七月丶8 小时前
别再手动凑 PR 了:这个 AI Skill 会按仓库习惯自动建分支、拆提交、提 PR
人工智能·设计模式·程序员
刀法如飞8 小时前
从程序员到架构师:6大编程范式全解析与实践对比
设计模式·系统架构·编程范式
九狼8 小时前
Flutter + Riverpod +MVI 架构下的现代状态管理
设计模式
静水流深_沧海一粟1 天前
04 | 别再写几十个参数的构造函数了——建造者模式
设计模式
StarkCoder1 天前
从UIKit到SwiftUI的迁移感悟:数据驱动的革命
设计模式
阿星AI工作室1 天前
给openclaw龙虾造了间像素办公室!实时看它写代码、摸鱼、修bug、写日报,太可爱了吧!
前端·人工智能·设计模式
_哆啦A梦2 天前
Vibe Coding 全栈专业名词清单|设计模式·基础篇(创建型+结构型核心名词)
前端·设计模式·vibecoding
阿闽ooo5 天前
中介者模式打造多人聊天室系统
c++·设计模式·中介者模式
小米4965 天前
js设计模式 --- 工厂模式
设计模式
逆境不可逃5 天前
【从零入门23种设计模式08】结构型之组合模式(含电商业务场景)
线性代数·算法·设计模式·职场和发展·矩阵·组合模式