zoukankan      html  css  js  c++  java
  • Trait

    从PHP的5.4.0版本开始,PHP提供了一种全新的代码复用的概念,那就是Trait。Trait其字面意思是"特性"、"特点",我们可以理解为,使用Trait关键字,可以为PHP中的类添加新的特性。

    熟悉面向对象的都知道,软件开发中常用的代码复用有继承和多态两种方式。在PHP中,只能实现单继承。而Trait则避免了这点。下面通过简单的额例子来进行对比说明。

    1. 继承 VS 多态 VS Trait

    现在有Publish.phpAnswer.php这两个类。要在其中添加LOG功能,记录类内部的动作。有以下几种方案:

    1. 继承

    2. 多态

    3. Trait

    1.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)之间的关系并不是子类与父类的关系。所以不推荐这样使用。

    1.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)原则。所以是不推荐这样实现的。

    1.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();

    可以看到,我们在没有增加代码复杂的情况下,实现了代码的复用。

    1.4. 结论

    继承的方式虽然也能解决问题,但其思路违背了面向对象的原则,显得很粗暴;多态方式也可行,但不符合软件开发中的DRY原则,增加了维护成本。而Trait方式则避免了上述的不足之处,相对优雅的实现了代码的复用。

    2. Trait的作用域

    了解了Trait的好处,我们还需要了解其实现中的规则,先来说一下作用域。这个比较好证明,实现代码如下:

    <?php
    class Publish {
        use Log;
        public function doPublish() {
            $this->publicF();
            $this->protectF();
            $this->privateF();
        }
    }
    $publish  = new Publish();
    $publish->doPublish();

    执行上述代码输出结果如下:

    public function
    protected function
    private function

    可以发现,Trait的作用域在引用该Trait类的内部是都可见的。可以理解为use关键字将Trait的实现代码Copy了一份到引用该Trait的类中。

    3. Trait中属性的优先级

    说到优先级,就必须要有一个对比的参照物,这里的参照对象时引用Trait的类及其父类。

    通过以下的代码来证明Trait应用中的属性的优先级:

    <?php
    trait Log
    {
        public function publicF()
        {
            echo __METHOD__ . ' public function' . PHP_EOL;
        }
        protected function protectF()
        {
            echo __METHOD__ . ' protected function' . PHP_EOL;
        }
    }
    
    class Question
    {
        public function publicF()
        {
            echo __METHOD__ . ' public function' . PHP_EOL;
        }
        protected function protectF()
        {
            echo __METHOD__ . ' protected function' . PHP_EOL;
        }
    }
    
    class Publish extends Question
    {
        use Log;
    
        public function publicF()
        {
            echo __METHOD__ . ' public function' . PHP_EOL;
        }
        public function doPublish()
        {
            $this->publicF();
            $this->protectF();
        }
    }
    $publish = new Publish();
    $publish->doPublish();

    上述代码的输出结果如下:

    Publish::publicF public function
    Log::protectF protected function

    通过上面的例子,可以总结出Trait应用中的优先级如下:

    1. 来自当前类的成员覆盖了 trait 的方法

    2. trait 覆盖了被继承的方法

    类成员优先级为:当前类>Trait>父类

    4. Insteadof和As关键字

    在一个类中,可以引用多个Trait,如下:

    <?php
    trait Log
    {
        public function startLog()
        {
            echo __METHOD__ . ' public function' . PHP_EOL;
        }
        protected function endLog()
        {
            echo __METHOD__ . ' protected function' . PHP_EOL;
        }
    }
    
    trait Check
    {
        public function parameterCheck($parameters) {
            // do sth
        }
    }
    
    class Publish extends Question
    {
        use Log,Check;
        public function doPublish($para) {
            $this->startLog();
            $this->parameterCheck($para);
            $this->endLog();
        }
    }

    通过上面的方式,我们可以在一个类中引用多个Trait。引用多个Trait的时候,就容易出问题了,最常见的问题就是两个Trait中如果出现了同名的属性或者方法该怎么办呢?这个时候就需要用到Insteadof 和 as 这两个关键字了.请看如下实现代码:

    <?php
    
    trait Log
    {
        public function parameterCheck($parameters)
        {
            echo __METHOD__ . ' parameter check' . $parameters . PHP_EOL;
        }
    
        public function startLog()
        {
            echo __METHOD__ . ' public function' . PHP_EOL;
        }
    }
    
    trait Check
    {
        public function parameterCheck($parameters)
        {
            echo __METHOD__ . ' parameter check' . $parameters . PHP_EOL;
        }
    
        public function startLog()
        {
            echo __METHOD__ . ' public function' . PHP_EOL;
        }
    }
    
    class Publish
    {
        use Check, Log {
            Check::parameterCheck insteadof Log;
            Log::startLog insteadof Check;
            Check::startLog as csl;
        }
    
        public function doPublish()
        {
            $this->startLog();
            $this->parameterCheck('params');
            $this->csl();
        }
    }
    
    $publish = new Publish();
    $publish->doPublish();

    执行上述代码,输出结果如下:

    Log::startLog public function
    Check::parameterCheck parameter checkparams
    Check::startLog public function

    就如字面意思一般,insteadof关键字用前者取代了后者,as 关键字给被取代的方法起了一个别名。

    在引用Trait时,使用了use关键字,use关键字也用来引用命名空间。两者的区别在于,引用Trait时是在class内部使用的。

  • 相关阅读:
    loadrunner-3-19LR常见函数
    loadrunner-3-18Service-Level Agreement(服务水平协议)
    loadrunner-3-15IP欺骗
    loadrunner-3-14集合点
    Python 静态类型检查 mypy 示例
    JavaScript 中 == 和 === 的区别
    Python 一键安装全部依赖包
    TypeError: 'NoneType' object is not subscriptable
    前端开发神器 VSCode 使用总结
    Next.js 配置接口跨域代理转发
  • 原文地址:https://www.cnblogs.com/linguoguo/p/8546662.html
Copyright © 2011-2022 走看看