其实在想文章题目时,有过纠结,虽然现在有些人将Mixin翻译为“混入”,不过感觉有点怪怪的,所以还是直接用了英文,至少不会出错。 言归正转。
现在排名靠前的面向对象的编程语言中,Java、C#等都是以单继承+接口来实现面向对象,但是这在一定程序了稀释了继承的力量, 因为在业内推荐以组合的方式使用类。这在一些常见的设计模式中有明显的体现,想想在GOF的23个设计模式中有多少个是使用了继承的呢? 大多数是以接口+组合的方式实现。其实作为一个类来说,它也比较难做,即要能代码复用,又得被实例化,偏向谁呢? 这个时候Mixin可能就有一些用武之地了。
Mixin最早起源于一个Lisp,Mixin鼓励代码重用,Mixin可以实现运行时的方法绑定,虽然类的属性和实例参数仍然是在编译时定义。 在面向对象编程语言,Mixin是一个提供了一些被用于继承或在子类中重用的功能的类,它类似于一种多继承, 但是实际上它是一种中小粒度的代码复用单元,而不直接用于实例化。 虽然这不是一种专业的方式进行功能复用,这在实现多继承的同时,在一定程序上避免了多继承的明显问题。
PHP和Java类似,也是单继承+接口。 我们知道,一个类可以实现任意数量的接口,这对一个类需要实现多个抽象的时候非常有用。 然而,对于要实现了多个接口的类,每个类都需要实现这些接口,而大多数情况下,这些接口都是可以共用的。 PHP并没有提供内置机制来定义和使用这些可重用代码,虽然我们可以对一地些接口使用一个抽象类来共用代码,但是如果这些类必须继承另一个抽象类呢? 就算是可以通过抽象类的多次继承实现代码的共用,但是整个继承体系将会变得非常复杂,如果不能实现重用,那么可能我们只得CTRL + C 和 CTRL + V了。 大多数的情况下我们其实只是需要重用一些代码而已。
虽然PHP在之前没有提供完善的解决方案,但在新发布PHP5.4中,出现了一个关键字trait。 通过这个关键字我们可以定义抽象为一个Trait,如下示例:
trait HelloWorld { public function sayHello() { echo 'Hello World!'; } } class TheWorldIsNotEnough { use HelloWorld; public function sayHello() { echo 'Hello Universe!'; } } $o = new TheWorldIsNotEnough(); $o->sayHello(); // echos Hello Universe!
更多关于Traits的信息, 请参考: Traits for PHP RFC
trait的实现
因为trait是一个语言结构,所以我们从zend_language_scanner.l文件中找到trait对应的关键字标识为:T_TRAIT 在zend_lang_parse.y文件中根据标识找到:
class_entry_type: T_CLASS { $$.u.op.opline_num = CG(zend_lineno); $$.EA = 0; } | T_ABSTRACT T_CLASS { $$.u.op.opline_num = CG(zend_lineno); $$.EA = ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; } | T_TRAIT { $$.u.op.opline_num = CG(zend_lineno); $$.EA = ZEND_ACC_TRAIT; } | T_FINAL T_CLASS { $$.u.op.opline_num = CG(zend_lineno); $$.EA = ZEND_ACC_FINAL_CLASS; } ;
T_TRAIT作为一个关键字和class并级,它作为一个另一种类型的类存在。它将与接口、类处于同一字段标识。 其定义在zend_complie.h文件,如下:
#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS 0x10 #define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS 0x20 #define ZEND_ACC_FINAL_CLASS 0x40 #define ZEND_ACC_INTERFACE 0x80 #define ZEND_ACC_TRAIT 0x120
以上的标识只是对应类的ce_flags结构,除此之外,在为的结构方面也有调整,如下:
struct _zend_class_entry { ...// 省略,木有修改 zend_class_entry **interfaces; // 接口列表 zend_uint num_interfaces; // 接口数 zend_class_entry **traits; // traits列表 zend_uint num_traits; // traits数 zend_trait_alias **trait_aliases; // 别名 zend_trait_precedence **trait_precedences; }
从上面的结构可以看出,PHP为traits增加了对应的字段存储。从PHP的介绍中我们可知,trait可以动态绑定,则其执行应该是中间代码执行期间。 因此,如果使用了trait关键字,将会有对应的中间代码:ZEND_BIND_TRAITS 生成。 ZEND_BIND_TRAITS关键字最终调用zend_complie.c文件中的zend_do_bind_traits函数完成traits类的绑定,如下代码:
ZEND_API void zend_do_bind_traits(zend_class_entry *ce TSRMLS_DC) /* {{{ */ { if (ce->num_traits <= 0) { return; } /* complete initialization of trait strutures in ce */ zend_traits_init_trait_structures(ce TSRMLS_CC); /* first care about all methods to be flattened into the class */ zend_do_traits_method_binding(ce TSRMLS_CC); /* then flatten the properties into it, to, mostly to notfiy developer about problems */ zend_do_traits_property_binding(ce TSRMLS_CC); /* verify that all abstract methods from traits have been implemented */ zend_verify_abstract_class(ce TSRMLS_CC); /* now everything should be fine and an added ZEND_ACC_IMPLICIT_ABSTRACT_CLASS should be removed */ if (ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) { ce->ce_flags -= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; } } /* }}} */
以上的绑定过程并不是和接口或类一样的的简单的合并操作,在合并操作之前需要处理引用和别名等情况, 此时类结构中的trait_aliases和trait_precedences就发挥作用了。 trait定义的结构最终也是一个类。