要理解水平复用,必须从 OOP 的 DRY 实践说起……
假设现在有三个业务类,它们都使用 ActiveRecord 模式实现持久化,那么代码看上去会像这样:
1 |
// 工具类 |
2 |
class ActiveRecord {}
|
3 |
// 业务类 |
4 |
class User extends ActiveRecord {}
|
5 |
class Article extends ActiveRecord {}
|
6 |
class Log extends ActiveRecord {}
|
然后,你发现 User 和 Article 的内容需要经常被读取,为了加快读取速度要用上 Cache,而 Log 大部分时候都是写入,很少读取,不需要使用 Cache。传统方法上,为了实现 Cache 读取,会把代码写成这样:
01 |
// 工具类 |
02 |
class ActiveRecord {}
|
03 |
// 业务类 |
04 |
class User extends ActiveRecord {
|
05 |
// 重载查询方法
|
06 |
function find() {
|
07 |
// 判断 Cache 在不在,读 Cache 然后返回
|
08 |
}
|
09 |
} |
10 |
class Article extends ActiveRecord {
|
11 |
// 重载查询方法
|
12 |
function find() {
|
13 |
// 判断 Cache 在不在,读 Cache 然后返回
|
14 |
}
|
15 |
} |
16 |
class Log extends ActiveRecord {}
|
这样明显有个问题,User 和 Article 使用 Cache 的代码是重复的!这时你可能想到把可以被 Cache 的类继承到另外一个叫 CachedActiveRecord 里面,把代码写成这样:
1 |
// 工具类 |
2 |
class ActiveRecord {}
|
3 |
class CachedActiveRecord extends ActiveRecord {}
|
4 |
// 业务类 |
5 |
class User extends CachedActiveRecord {}
|
6 |
class Article extends CachedActiveRecord {}
|
7 |
class Log extends ActiveRecord {}
|
这样的确解决了重复 Cache 代码的问题,但另外一个问题又来了:如果我要在 User 和 Article 实现不同的 Cache 策略呢?
这种方式只能实现固定的 Cache 策略,虽然可以通过 if..else../switch 来实现,但这样显然不符合 OOP 的原则。
于是有人实现了 Mixin 模式,把需要重用的代码抽到一个独立的类里面,然后令所有类继承都一个 Mixin 工具类,工具类通过 PHP 特有的 __set / __get / __call 等方式把需要重用的类塞到当前类里面,代码会变成这样子:
01 |
// 工具类 |
02 |
class Mixin {}
|
03 |
class ActiveRecord extends Mixin {}
|
04 |
class CacheBehavior extends Mixin {
|
05 |
// 重载源对象的查询方法
|
06 |
function find( $self ) {
|
07 |
// 通过传入参数获取原对象实例
|
08 |
}
|
09 |
} |
10 |
// 业务类 |
11 |
class User extends ActiveRecord {}
|
12 |
class Article extends ActiveRecord {}
|
13 |
class Log extends ActiveRecord {}
|
14 |
|
15 |
// 调用方式 |
16 |
$user = new User();
|
17 |
$user ->attach( new CacheBehavior());
|
18 |
$user ->find(); // 此时调用的是 CacheBehavior 的 find 方法
|
这样貌似解决了代码重用的问题了……且慢!你没发现这样的话所有类都必须继承 Mixin 工具类吗?如果某天需要用上一个不 Mixin 的 ActiveRecord 那岂不是需要把它复制一份?
于是这里就需要 trait 语法的粉墨登场了!先把 CacheBehavior 变成 trait,代码变成这样:
01 |
// 工具 trait |
02 |
trait CacheBehavior { |
03 |
function find() {}
|
04 |
} |
05 |
// 工具类 |
06 |
class ActiveRecord {}
|
07 |
// 业务类 |
08 |
class User extends ActiveRecord {
|
09 |
use CacheBehavior;
|
10 |
} |
11 |
class Article extends ActiveRecord {
|
12 |
use CacheBehavior;
|
13 |
} |
14 |
class Log extends ActiveRecord {}
|
这样就实现了刚才使用了 Mixin 工具类才能实现的效果,代码量更少更简洁!
进一步思考,其实 ActiveRecord 也属于工具类而不是业务类(领域模型),那实际上也是应该拿出来的,于是最后代码写成了:
01 |
// 工具 trait |
02 |
trait ActiveRecord {} |
03 |
trait CacheBehavior { |
04 |
function find() {}
|
05 |
} |
06 |
// 业务类 |
07 |
class User {
|
08 |
use ActiveRecord, CacheBehavior;
|
09 |
} |
10 |
class Article {
|
11 |
use ActiveRecord, CacheBehavior;
|
12 |
} |
13 |
class Log {
|
14 |
use ActiveRecord;
|
15 |
} |
16 |
|
17 |
// 调用方式 |
18 |
$user = new User();
|
19 |
$user ->find(); // 此时调用的是 CacheBehavior 的 find 方法,省略了 attach 步骤
|
代码终于和谐了!当然,ActiveRecord 和 CacheBehavior 之间的关联性还有待解决,不过这里就不纠结这个问题了=v=b
使用 trait 之后,某天出现了一个新的业务类 Manager 也只需轻松继承或 use 一下即可:
01 |
// 这样 |
02 |
class User {
|
03 |
use ActiveRecord, CacheBehavior;
|
04 |
} |
05 |
class Manager extends User {}
|
06 |
// 或者 |
07 |
class Manager {
|
08 |
use ActiveRecord, CacheBehavior;
|
09 |
} |
10 |
|
11 |
// 直接调用 |
12 |
$manager = new Manager();
|
13 |
$manager ->find(); // 同样是使用 CacheBehavior 的 find 方法
|
从此对“水平复用”代码表示毫无鸭梨!
更多说明请查看官方 wiki:https://wiki.php.net/rfc/horizontalreuse