转载链接:https://www.jb51.net/article/105579.htm
1.trait简介
1.1 特性
(1)优先级:当前类中的方法 > trait中的方法 > 基类中的方法
(2)多个trait使用,分隔
(3)trait 冲突使用insteadof操作符号来指定使用冲突方法中的哪一个
(4)as操作符可以为方法设置别名,也可以修改trait中的方法的访问控制
(5)其他trait也可以使用trait。
(6)Traits 可以被静态成员静态方法定义。
(7)在引用Trait时,使用了use关键字,use关键字也用来引用命名空间。两者的区别在于,引用Trait时是在class内部使用的。比如thinkphp5框架中use concern\ModelRelationQuery;
(8)Trait 定义了一个属性后,类就不能定义同样名称的属性(名字,值和访问控制相同就没问题),否则会产生 fatal error。
1.2 优点
解决代码复用,解决PHP单继承的问题;
1.3 区别
trait和class的区别是trait不能被实例化
1.4 代码
<?php
class Base{
// public function sayHello(){
// echo "hello";
// }
}
trait Mood{
public $a =1;
public function sayMood()
{
echo "!";
}
}
trait Sayworld{
public function sayHello()
{
// parent::sayHello();
echo "world";
}
abstract public function getWorld();
public static function doSomething()
{
echo "static function";
}
}
class MyHelloWorld extends Base{
use Sayworld,Mood{
sayHello as public;
}
public $a =1 ; //=2值不同就会fatal error
public function getWorld()
{
return "getWorld";
}
// public function sayHello()
// {
parent::sayHello();
// echo "MyHelloWorld";
// }
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayMood();
$o::doSomething();
trait Man{
use Sayworld;
public function eat()
{
echo "man word";
}
public function talk(){
echo "Chinese";
}
public function inc()
{
static $i=1;
$i++;
echo $i;
}
}
trait Woman{
public function eat()
{
echo "woman home";
}
public function talk()
{
echo "English";
}
}
class Person{
use Man,Woman{
Woman::eat insteadof Man;
Man::talk insteadof Woman;
Woman::talk as talks;
}
public function getWorld()
{
// TODO: Implement getWorld() method.
}
}
class Animals{
use Man;
public function getWorld()
{
// TODO: Implement getWorld() method.
}
}
$person = new Person();
//$person->eat();
//$person->talks();
//$person->sayHello();
$person->inc();
(new Animals())->inc(); //静态变量不会改变 都是2
2.使用场景
熟悉面向对象的都知道,软件开发中常用的代码复用有继承和多态两种方式。在PHP中,只能实现单继承。而Trait则避免了这点。下面通过简单的额例子来进行对比说明。
- 继承 VS 多态 VS Trait
现在有Publish.php和Answer.php这两个类。要在其中添加LOG功能,记录类内部的动作。有以下几种方案:
继承
多态
Trait
2.1. 继承
如图:
代码结构如下:
// Log.php
<?php
Class Log
{
public function startLog()
{
// echo ...
}
public function endLog()
{
// echo ...
}
}
// Publish.php
<?php
Class Publish extends Log
{
}
// Answer.php
<?php
Class Answer extends Log
{
}
可以看到继承的确满足了要求。但这却违背了面向对象的原则。而发布(Publish)和回答(Answer)这样的操作和日志(Log)之间的关系并不是子类与父类的关系。所以不推荐这样使用。
2.2. 多态
如图:
// Log.php
<?php
Interface Log
{
public function startLog();
public function endLog();
}
// Publish.php
<?php
Class Publish implements Log
{
public function startLog()
{
// TODO: Implement startLog() method.
}
public function endLog()
{
// TODO: Implement endLog() method.
}
}
// Answer.php
<?php
Class Answer implements Log
{
public function startLog()
{
// TODO: Implement startLog() method.
}
public function endLog()
{
// TODO: Implement endLog() method.
}
}
记录日志的操作应该都是一样的,因此,发布(Publish)和回答(Answer)动作中的日志记录实现也是一样的。很明显,这违背了DRY(Don’t Repeat Yourself)原则。所以是不推荐这样实现的。
2.3. Trait
如图:
// Log.php
<?php
trait Log{
public function startLog() {
// echo ..
}
public function endLog() {
// echo ..
}
}
// Publish.php
<?php
class Publish {
use Log;
}
$publish = new Publish();
$publish->startLog();
$publish->endLog();
// Answer.php
<?php
class Answer {
use Log;
}
$answer = new Answer();
$answer->startLog();
$answer->endLog();
可以看到,我们在没有增加代码复杂的情况下,实现了代码的复用。
2.4. 结论
继承的方式虽然也能解决问题,但其思路违背了面向对象的原则,显得很粗暴;多态方式也可行,但不符合软件开发中的DRY原则,增加了维护成本。而Trait方式则避免了上述的不足之处,相对优雅的实现了代码的复用。