PHP 5.4 全新 trait 语法
文章转载于:http://blog.makingware.com/?p=97
发表于 2011 年 06 月 08 日 由 Jeffrey Au <!-- .entry-meta -->最近看 PHP 5.4 的 Changelog 时发现新增了 Trait 语法,根据 wiki 的说明,这个语法是为了解决代码的“水平复用”而诞生的。什么是“水平复用”呢?这里用一个相对简单的例子说明一下:?
要理解水平复用,必须从 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 读取,会把代码写成这样:
view sourceprint?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 里面,把代码写成这样:
view sourceprint?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