一般来说,代码的复用有“组合”与“继承”两种方式,我们更推荐使用组合的方式,它为我们带来了更大的灵活性。而继承,应当是“组合的一种组合方式”,只有特定组合的组合方式多次产生时,才应该用基类的方式将它固化下来。
在 PHP 中,我们一般使用 Trait 来实现组合,通过抽象类、接口类或基类的方式来实现继承。
一、组合——更灵活的代码复用方式
Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。
Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。
php.net
道丁快搜中,大量使用了 Trait 机制。
我们可以根据一些类具有的某种相同“特征”,抽象出一些公共的方法、属性。
这些公共代码可以放在一个集合中,这个集合就叫做 Trait。
组合,为我们带来了更大的灵活性
我们可以在一个类中,通过引入 A Trait,获得鉴权的逻辑;在另一个类中,通过引入 A 和 B 两个 Trait,兼得 Redis 连接池和 Http 请求的通用方法。
在使用组合达到编码快感的同时,大量的 Trait 也为我们带来了很多的麻烦:
- 因为 Trait 组合是同级的,不存在继承。大量的 Trait 堆积在我们面前的时候,我们常常无法快速准确地找到目标,对刚接触项目的新同学来说尤其如此。
- 虽然 Trait 之间没有继承与多态,但是不同的 Trait 却可能产生重复的组合。比如鉴权 trait 已经组合了 Redis Trait,但开发者在不清楚黑盒逻辑的情况下,就会很轻易地产生问题。
二、继承——组合的组合
经过第一节的论述,我们意识到毫无克制地使用 Trait,也会对我们造成很多困扰。
混乱,就需要断舍离。
我们需要某种规则进行整理。
我们将这一节定得比较绕的标题,抽象成比较简单的数学模型:
\[BaseClass=\sum_{i=0}^n Trait_n\]别慌,我来解释下。先解释下各个符号的意义:
- BaseClass 代表一个基类。这里特指一个适用于某种特定场合、具有某种特征的基类
- Trait 代表一个PHP中的 trait,直译过来叫“特征”,是 PHP5 之后的一个新特性。
- ∑ 读作“西格玛(sigma)”,是求和的意思,相当于一个 for 循环。i=0 是初始值,最大到 n。
上面的公式用 PHP 代码表示出来就很直观了:
$abstract_class = [];
for($i=0; $i<=n; $i++) {
$abstract_class[] = $trait[$i];
}
(这里感慨下数学之美,五行代码表达出来的东西,用数学建模只用一行)
从上面的讨论过程可以得出以下定义:
这条结论我们暂且将它定为一个公理,后续我们会依赖这条定义来讨论。