05 为何说要多用组合少用继承?如何决定该用组合还是继承?

高级工程师为何说要多用组合少用继承?在工作中,假如你工作了,或者你参加过代码review,那你应该听到过这句话。我以前只是听说过,但是没有使用过,哈哈哈哈哈,今天学习下,以后少被骂。

看过前面的文章,到这里用我通俗的感觉就是代码设计演进的一个过程,抽象类,接口比继承的优点。这一章节是组合使用对于继承的优点。组合优于继承,多用组合少用继承。为什么不推荐使用继承?组合相比继承有哪些优势?如何判断该用组合还是继承?今天,我们就围绕着这三个问题,来详细讲解一下这条设计原则。

1. 为什么不推荐使用继承?

前面在讲继承和抽象类的以及接口的区别时,已经讲过继承的缺点,继承主要是表示is-a关系,主要是解决代码复用问题,但是假如继承层级过深,那么就会造成代码复杂且冗余,很难维护。

目前的继承关系还比较简单,层次比较浅,也算是一种可以接受的设计思路。我们再继续加点难度。目前场景中,我们只关注"鸟会不会飞",但如果我们还关注"鸟会不会叫",那这个时候,我们又该如何设计类之间的继承关系呢?

这种情况再来几个,那真的是头疼,假如你要修改一个功能,你需要先去改不会飞的鸟类,再去修改会飞的鸟类,照这样下去,你会因为这种低级且重复的工作而烦累,技术上也止步不前。

2. 什么是组合?

面向对象编程中,组合(Composition)是一种关系,表示一个类包含另一个类的对象,而这种关系通常表达了 "有一个" 的关联。这是一种对象关联的形式,与继承相比,组合更注重 "包含" 关系而不是 "是一个" 关系。

js 复制代码
// 定义引擎类
class Engine {
    void start() {
        System.out.println("Engine starting...");
    }
}

// 定义汽车类,通过组合引擎实现
class Car {
    private Engine engine;  // 使用组合关系,Car 包含一个 Engine 对象

    // 构造函数,通过组合关系初始化 Engine 对象
    Car() {
        this.engine = new Engine();
    }

    // 启动汽车,委托引擎启动
    void start() {
        engine.start();
        System.out.println("Car starting...");
    }
}

在上述示例中,Car 类通过组合关系包含了一个 Engine 对象。这意味着 Car 不是继承自 Engine,而是在其内部包含了一个 Engine 对象。通过这种方式,Car 类可以重用 Engine 类的功能,同时保持了更松散的耦合。

组合的优势

  • 灵活性: 可以轻松更换或升级组成对象,而不影响包含它们的类。
  • 复用: 通过组合可以重用其他类的功能,而不必继承整个类的实现。
  • 简化: 降低了系统的复杂性,因为组合通常比继承更简单。

3. 组合相比继承有哪些优势?

困难总比办法多,所以优秀的程序员们想尽各种方法来制造困难,再解决困难。组合(composition)、接口、委托(delegation)三个技术手段,一块儿来解决刚刚继承存在的问题。

  1. 针对"会飞"这样一个行为特性,我们可以定义一个 Flyable 接口,只让会飞的鸟去实现这个接口。对于会叫、会下蛋这些行为特性,我们可以类似地定义 Tweetable 接口、EggLayable 接口。
js 复制代码
public interface Flyable {
    void fly();
}

public interface Tweetable {
    void tweet();
}

public interface EggLayable {
    void layEgg();
}

public class Ostrich implements Tweetable, EggLayable {// 鸵鸟
    //... 省略其他属性和方法...
    
    @Override
    public void tweet() { //... }
    
    @Override
    public void layEgg() { //... }
}
public class Sparrow impelents Flayable, Tweetable, EggLayable {// 麻雀
    //... 省略其他属性和方法...
    @Override
    public void fly() { //... }
    
    @Override
    public void tweet() { //... }
    
    @Override
    public void layEgg() { //... }

但是这仅仅是解决了单继承的问题,套用了接口可以多实现的特性

  1. 每个会下蛋的鸟都要实现一遍layEgg() 方法,并且实现逻辑是一样的,这就会导致代码重复的问题。那这个问题又该如何解决呢?

针对三个接口再定义三个实现类,它们分别是:实现了 fly() 方法的 FlyAbility类、实现了 tweet() 方法的 TweetAbility 类、实现了 layEgg() 方法的 EggLayAbility 类。然后,通过组合委托技术来消除代码重复。具体的代码实现如下所示:

js 复制代码
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 关系来替代;
  • 多态特性我们可以利用接口来实现;
  • 代码复用我们可以通过组合和委托来实现。

从理论上讲,通过组合、接口、委托三个技术手段,我们完全可以替换掉继承,在项目中不用或者少用继承关系,特别是一些复杂的继承关系。

4. 如何判断该用组合还是继承?

组合是通过定义更多的类和接口,来细粒度的动态嫁接了需要继承获取到的Feature,比如上面的Car的例子,如果使用继承,那你只能让car继承Engine类,但是通过在Car类里动态一个Engine对象,从而获取到Engine的能力。但是这也降低了代码的整洁。所以说,使用组合也是很考验技术的学问。

使用继承 : 如果类之间的继承结构稳定(不会轻易改变),层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。

使用组合 :系统越不稳定,继承层次深,继承关系复杂

除此之外,还有一些设计模式、特殊的应用场景,会固定使用继承或者组合。

相关推荐
·薯条大王2 小时前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
用户938515635073 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformer.js 全链路实战
前端·设计模式·typescript
ZJH__GO5 小时前
网络编程v4pro--实现聊天室文件传输功能
java·服务器·网络·计算机网络
程序员黑豆6 小时前
Windows 系统 Java 环境变量配置全攻略:解决“不是内部或外部命令”
java·前端·ai编程
命运之光6 小时前
【C语言完整代码】就诊信息管理系统
java·c语言·开发语言
凤山老林6 小时前
Spring Boot @Async 线上实战:从默认配置到生产级线程池治理
java·spring boot·后端
marvelyu7 小时前
每天10分钟学会OceanBase系列(Day 20):跨机房容灾实战——构建多数据中心高可用架构
java·大数据·数据库
AI人工智能+电脑小能手8 小时前
大白话说Java设计模式-04-工厂方法模式(业务实战篇)
java·设计模式·工厂方法模式·架构设计·代码解耦
BUG指挥官8 小时前
Sa-Token和Spring Security对比
java·后端·spring
小当家.1058 小时前
深入理解 ReAct Agent:从原理到 Java 实战
java·react.js·agent·react·架构设计·agent设计