PHP中Traits详解及如何利用Traits实现代码重用

墨蓝 2024-04-19 12:39 131阅读 0赞

在正题开始之前,先来聊下PHP面向对象的三大特性:

  • 封装:把客观事物封装成抽象的类,且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
  • 继承:让某个类型的对象获得另一个类型的对象的属性的方法,它支持按级分类的概念。
  • 多态:一个类实例的相同方法在不同情形有不同表现形式。
    我们说一下继承这个问题,PHP是一门单继承的语言,不支持多继承,一次只能继承一个类。但是上有政策,下有对策,我们可以通过两种方法来现在,

1.接口interface

举一个简单的例子:

  1. <?php
  2. interface A
  3. {
  4. public function testA();
  5. }
  6. interface B
  7. {
  8. public function testB();
  9. }
  10. class C implements A, B
  11. {
  12. public function testA()
  13. {
  14. echo "这是接口A<br>";
  15. }
  16. public function testB()
  17. {
  18. echo "这是接口B<br>";
  19. }
  20. public function testC()
  21. {
  22. echo "这是自己<br>";
  23. }
  24. }
  25. $obj = new C();
  26. $obj->testA();
  27. $obj->testB();
  28. $obj->testC();

输出:

  1. 这是接口A
  2. 这是接口B
  3. 这是自己

上面的这个例子,两个接口A和B,然后类C为子类,通过interface方法来达到子类C可调用A,B接口中的所有方法,达到多继承效果。当然,你也可以:先让子类extends一个基类,然后再implements一个接口达到多继承效果

  1. class C extends B implements A{ }

2.traits

这个traits也是这篇文章想重点说的。
PHP在5.4.0版本以前是没有是没有traits。那么它是个什么东西呢?
我的理解是引入traits机制是为了解决PHP多继承限制的问题,实现代码的重用。那有人说了上面不是说了可以通过interface实现,但我个人理解这个地方本身是不矛盾的。我们采用继承是为了更加简洁,高效的开发,充分使用OOP思想来使我们的代码更加完善,合理。traits的使用更大程度是为了提高代码的可重用性,但是他可以解决多继承的限制问题。类与类之间的extends自然在某些方便更加的有效,但是开发过程中遇到的问题会很多,合理选择方法来解决问题会更好。比如代码的重用性,或者你觉得traits比interface解决多继承限制更方便的时候。看下面这段话:

traits是一种在php等单一继承语言中重用代码的机制。特性旨在通过允许开发人员在不同类层次结构中的几个独立类中自由重用方法集来减少单个继承的某些限制。在定义特征和类的组合语义时,减少了复杂性,避免了与多重继承和混合相关的典型问题。
traits类似于类,但仅用于以细粒度和一致的方式对功能进行分组。不可能单独实例化一个特征。它是对传统继承的补充,支持行为的水平组合;也就是说,不需要继承就可以应用类成员。

3.实例

3.1:独立实例的优点

直接看例子体会一下

  1. <?php
  2. class A
  3. {
  4. public static $testA;
  5. }
  6. class B extends A
  7. {
  8. }
  9. class C extends A
  10. {
  11. }
  12. B::$testA = 'hello';
  13. C::$testA = 'world';
  14. echo B::$testA.' '.C::$testA;
  15. //输出:world world
  16. <?php
  17. trait A
  18. {
  19. public static $testA;
  20. }
  21. class B
  22. {
  23. use A;
  24. }
  25. class C
  26. {
  27. use A;
  28. }
  29. B::$testA = 'hello';
  30. C::$testA = 'world';
  31. echo B::$testA.' '.C::$testA;
  32. //输出:hello world

很显然我们想要的是输出:hello world。使用traits很简单就实现了。这个例子确实比较特殊一点,牵涉到静态属性了。在这里也要强调一点:
与继承不同,如果trait中具有静态属性,则使用该trait的每个类都具有这些属性的独立实例。所以使用trait的例子成功输出了:hello world。

3.2:优先权问题

我们使用过程中肯定会存在子类继承基类,然后又想重用代码使用traits的场景。这时候如果子类,基类,trait中的方法名相同会怎样呢?

  1. <?php
  2. class A
  3. {
  4. public function say()
  5. {
  6. echo 'hello';
  7. }
  8. }
  9. trait B
  10. {
  11. public function say()
  12. {
  13. parent::say();
  14. echo 'world';
  15. }
  16. }
  17. class C extends A
  18. {
  19. use B;
  20. // public function say()
  21. // {
  22. // echo 'test';
  23. // }
  24. }
  25. $obj = new C();
  26. $obj->say();
  27. //目前输出:helloworld,如果注释放开,输出:test,如果将trait B中的 parent::say(); 注释掉,输出:world

通过上面这个例子,很清晰的表达出了关于结合使用traits和继承时候优先权问题,那就是:子类 >trait>基类,也就是来自基类的继承成员被trait插入的成员覆盖。优先顺序是当前类的成员覆盖Trait方法,Trait又覆盖继承的方法。

3.3:对3.2的一个补充,关于多重trait的使用
  1. <?php
  2. trait A
  3. {
  4. public function hello()
  5. {
  6. echo 'hello';
  7. }
  8. }
  9. trait B
  10. {
  11. public function world()
  12. {
  13. echo 'world';
  14. }
  15. }
  16. class C
  17. {
  18. use A,B;
  19. }
  20. $obj = new C();
  21. $obj->hello();
  22. $obj->world();
  23. //输出:helloworld
3.4:解决多个trait中具有同名方法的问题

这里说的就是,如果有多个trait中的方法名都相同,那么在use的时候会产生致命错误。官方的给的解决方法是使用insteadof运算符解决,并且提供了as运算符添加别名。

  1. <?php
  2. trait A
  3. {
  4. public function hello()
  5. {
  6. echo 'A hello';
  7. }
  8. public function world()
  9. {
  10. echo 'A world';
  11. }
  12. }
  13. trait B
  14. {
  15. public function hello()
  16. {
  17. echo 'B hello';
  18. }
  19. public function world()
  20. {
  21. echo 'B world';
  22. }
  23. }
  24. class C
  25. {
  26. use A,B{
  27. A::hello insteadof B;//使用insteadof确定区别于B,使用来自A的hello
  28. B::world insteadof A;//使用insteadof确定区别于A,使用来自B的world
  29. B::hello as Bhello;//使用as运算符对B trait中的hello方法进行别名定义
  30. }
  31. }
  32. $obj = new C();
  33. $obj->hello();
  34. $obj->world();
  35. echo '<br />';
  36. $obj->Bhello();
  37. //输出:A helloB world
  38. //B hello
3.5:关于use的问题

我们知道PHP中的use一共有三种定义:

  • 命名空间的use使用
  • 闭包use的使用
  • traits的use使用
    我想说的是traits的use操作符(类内部)和命名空间的use操作符(类外部)解析名称的方式不同,和闭包的中的意义更不一样。
    例1:

    <?php

    namespace App;
    use Models\Order;

//这里的use代表使用 \Models\Order 类,就是说这里的Order的前置选项是可选的,你可以是:use Mymodels\Order;
例2:

  1. <?php
  2. namespace App;
  3. class Test
  4. {
  5. use MyTraits\TraitA;
  6. }

//表面意义是使用命名空间是MyTraits下的TraitA,但是这里切记一点,traits的use使用,必须以当前的命名空间为基准,所以这里实际就是:use \App\MyTraits\TraitA;

3.6:关于私有属性访问的问题

这一点本来使用的地方比较少,但是最为一个有趣的特点。我写个小例子大家看下。
我们都知道在extends继承中,子类的私有属性不能被子类访问,但是trait可以访问组成类的私有属性或方法,反之亦然。

  1. <?php
  2. trait A
  3. {
  4. function hello() {
  5. echo $this->hello;
  6. }
  7. }
  8. class B
  9. {
  10. use A;
  11. private $hello = "hello world!";
  12. }
  13. $obj= new B();
  14. $obj->hello();
  15. //输出:hello world!

4.总结

总之,traits的褒贬不一,看到过一篇文章,很早很早之前的一篇文章,说的就是traits的缺点,比如会经常滥用等等。有兴趣的可以看下这篇文章:Are Traits The New Eval?
优点确实也很多:它让开发者在多个类之间水平的重用代码片段,并且这些类不用在同一个继承层次机构中,为开发者提供了一种我觉得是轻量级的代码重用机制,这也是PHP从5.4.0引入Traits机制的最重要的目的,可以有效的帮助我们改进程序的设计,去除重复代码使其更加简洁高效。

发表评论

表情:
评论列表 (有 0 条评论,131人围观)

还没有评论,来说两句吧...

相关阅读

    相关 php Traits 详解

    PHP是单继承的语言,在PHP 5.4 Traits出现之前,PHP的类无法同时从两个基类继承属性或方法。php的Traits和Go语言的组合功能有点类似, 通过在类中使用u

    相关 PHPTrait 特性

    Trait是在PHP5.4中加入的,它既不是接口也不是类。主要是为了解决单继承语言的限制。是PHP多重继承的一种解决方案。例如,需要同时继承两个 Abstract Class,

    相关 PHPTrait详解

    > php从以前到现在一直都是单继承的语言,无法同时从两个基类中继承属性和方法,为了解决这个问题,php出了Trait这个特性 用法:通过在类中使用use 关键字,声明要组合

    相关 PHP Trait 使用指南(life)

    通过更好地组织代码和代码复用来最大程度地减少代码重复是面向对象编程的重要目标。但是在 PHP 中,由于使用单一继承模型的局限性,有些时候要做到这些可能会比较困难。您可能有一些要

    相关 PHPTrait详解

    > php从以前到现在一直都是单继承的语言,无法同时从两个基类中继承属性和方法,为了解决这个问题,php出了Trait这个特性 用法:通过在类中使用use 关键字,声明要组合