您的位置:首页 > PHP > 关于Mixin和Trait

关于Mixin和Trait

其实在想文章题目时,有过纠结,虽然现在有些人将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定义的结构最终也是一个类。

本文地址:关于Mixin和Trait    文章出处:PHP源码阅读,PHP设计模式,PHP学习笔记,项目管理-胖胖的空间

转载请以链接形式注明原始出处和作者,谢绝不尊重版权者抄袭!

1条留言我要留言

  •   1 - TonySeek  |  2011-07-11 at 2:55 pm  

    加入这个特性让我很激动,和 Ruby 的 block 一样~

必填

必填,绝不公开

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word