为什么 Rust 等新兴编程语言集体放弃继承选择组合
首先,我先叠个甲,我并不认同“组合由于继承”这种太过于绝对化的观点,其实只要你对继承和组合这两者都有过思考的话,你就会明白两者是各有优缺点的,只是组合更适合现在的开发模式。
在面向对象编程(OOP)的发展历程中,继承是代码复用的核心手段,C++、Java、C# 等编程语言都将其作为核心特性。但是近年来,以 Rust、Go、Swift 为代表的新兴编程语言,却纷纷放弃了类继承机制,转而选择组合模式。这并非偶然,而是开发者在长期实践中,对代码复用、可维护性、灵活性的追求而做出的更优解,今天,我们一起来聊一聊,继承与组合各自的优劣势,以及新兴编程语言放弃继承选择组合的底层逻辑。
理清概念
继承和组合都是解决代码复用的问题而提出的手段,只是两者实现代码复用的方式不同而已,用一句话区别就是:
继承是
is-a的关系,组合是has-a的关系。
继承:子类是父类的“特殊化”
继承允许一个类(子类/派生类)从另一个类(父类/基类)继承属性和方法,子类自动获得父类的所有公开能力,同时可以重写父类方法、扩展新功能。
最经典的例子就是 Animal 与 Dog,Dog 是 Animal 的一种,所以 Dog 类继承 Animal 类,就能复用 Animal 的呼吸、进食等方法,再添加自己的吠叫方法。
class Animal {
public void breath() {
System.out.println("呼吸...");
}
public void eat() {
System.out.println("吃东西...");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("吠叫...");
}
}
在支持继承的语言中,继承还分为单继承(如 Java)和多继承(如 C++),单继承避免了多继承存在的歧义问题,但是也限制了代码复用的灵活性。多继承虽然能复用多个父类的功能,却会引入菱形继承等复杂问题,这也是大部分支持继承的编程语言均不支持多继承的原因。
组合:对象是组件的“集合体”
组合则是将多个独立的组件(对象)组合成一个新对象,新对象通过持有其他组件的实例,复用组件的功能,而非直接继承其代码。
例如 Car 与 Engine,Car 不是 Engine 的一种,而是 Car 拥有一个 Engine,同时还可以拥有 Wheel、Dashboard 等组件,通过调用这些组件的方法,实现行驶、显示车速等功能。
组合的核心是委派,新对象不直接实现所有功能,而是将具体功能委派给内部的组件来实现,它只是进行组合,可以进行自由替换、扩展,这也是其灵活性的核心来源。
继承与组合的优与劣
没有绝对的好与坏的设计,只有更适合当下的设计,这就是我的态度。这里我们将从代码复用、可维护性、灵活性、内存安全等维度,全面对比两者的优与劣,这样也就更能理解新兴编程语言的选择逻辑。
继承的优势:简洁直观,快速复用
继承的代码复用成本是极低的,子类只需要继承父类就能够实现父类的能力,无需重复编写,这非常适合层级清晰的简单场景。例如定义 Student、Teacher 类时,都可继承 Person 类,复用姓名、年龄等属性和说话、吃饭等方法,极大的减少了冗余代码。
继承天然的层级结构清晰,符合人类对分类的认知习惯,这非常有助于开发者理解类之间的逻辑关系。例如 Animal -> Mammal(哺乳动物) -> Dog,层级明确,代码的可读性更强。
继承的劣势:耦合过高
在继承当中,子类与父类是深度绑定的,父类的任何修改(如方法签名变更、属性删除),都可能导致所有的子类崩溃。例如父类 Animal 的“进食”方法增加了一个参数,所有继承自它的子类可能都需要修改实现,维护成本随子类数量增加呈指数级上升。
继承还存在着功能复用受限的问题,继承意味着必须继承父类的全部功能,无法做到只继承部分的功能,比如 Bird 类具有 eat、fly 的能力,但是 Penguin(企鹅)并不具有 fly 的能力,它具有 swim 的能力。
组合的优势:低耦合,易维护
组合是低耦合的,各个组件是独立的,可以按需组合,只复用自己需要的能力,像上面提到的继承存在复用受限的问题,在组合这里这个问题完全不存在,Penguin 只需要去组合 eat、 swim 的组件就好了。
组合的劣势:前期设计成本高,代码略显繁琐
使用组合需要提前拆分功能,设计独立的组件,相比继承的“直接继承”,前期设计成本更高。比如实现 Car 类,需要先拆分出 Engine、Wheel 等组件,再设计组件的接口。这在简单场景下就显得很繁琐了,这时候反而没有继承的“一键复用”来得好用。
组合不存在层级结构的概念,对于复杂系统,若组件拆分不合理,可能导致组件之间的关系混乱,增加开发者的理解成本,这对设计者的能力其实有更高的要求。
为什么新兴语言集体放弃继承
最重要的原因是现代软件迭代速度太快、需求变化太频繁。继承的强耦合特性会导致牵一发而动全身,严重影响迭代效率。而组合的低耦合特性,允许开发者在不修改原有代码的前提下,通过添加、替换组件实现新功能,完美契合开闭原则(对扩展开放,对修改关闭)。
总结
综上,不是继承不好,而是组合更适配未来。Rust 等新兴语言放弃继承,本质上是对现代软件开发痛点的回应。继承的强耦合已经难以适配复杂系统的迭代,而组合的低耦合更符合未来软件模块化、可扩展的发展趋势。