什么是 Trait

Trait 是从 PHP 5.4 加入的一种细粒度代码复用的语法。以下是官方手册对 Trait 的描述:

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

什么是 Trait ?

其实说通俗一点,就是能把重复的方法拆分到一个文件,通过 use 引入以达到代码复用的目的。

那么,我们应该怎么样去拆分我们的代码才是合适的呢?我的看法是这样的:

Trait,译作 “特性”、“特征”、“特点” 。那么问题就来了:什么才是特性?

一个销售公司有很多种产品:电视,电脑与鼠标垫,卡通手办等。其中鼠标垫与卡通手办是非卖品,只用于赠送。

那么这里的 “可卖性” 就是一个特性,非卖品是没有价格的。我们便可以抽象出 “可卖性” 这个 Trait 来:

trait Sellable
{
    protected $price = 0;

    public function getPrice()
    {
        return $this->price;
    }

    public function setPrice(int $price)
    {
        $this->price = $price;
    }
}

当然我们所有的产品都会有品牌与其它基本属性,所以我们通常会定义一个产品类:

class Pruduct
{
    protected $brand;
    //...

    public function __construct($brand)
    {
        $this->brand = $brand;
    }

    public function getBrand()
    {
        return $this->brand;
    }

    //...
}

我们的电视与电脑类:

class TV extends Pruduct
{
    use Sellable;
    //...

    public function play()
    {
        echo "一台 {$this->brand} 电视在播放中...";
    }

    //...
}

class Computer extends Pruduct
{
    use Sellable;

    protected $cores = 8;
    //...

    public function getNumberOfCores()
    {
        return $this->cores;
    }

    //...
}

而鼠标垫与手办等礼品是不可卖的:

class Gift extends Pruduct
{
    protected $name;

    function __construct($brand, $name)
    {
        parent::__construct($brand);
        $this->name = $name;
    }

    //...
}

上面的这个例子中,“可卖性” 便是部分商品的一个特性,也可以理解为商品的一个归类。你也许会说,我也可以再添加一个 Goods 类来完成上面的例子啊,Goods 继承 Product,再让所有可卖的商品继承于 Goods 类,把价格属性与方法写到 Goods 里,同样可以代码复用啊。的确,这没啥问题。但是你会发现:你有多个需要区别的特性时,由于 PHP 只有单继承的原因,你不得不组合很多个基类出来,将他们层叠,最终得到的树状结构是很复杂的。这也是 Trait 所带来的优势:随意组合,代码清晰。

其实还有很多例子,比如可飞行的,那么把飞行这个特性所具有的属性(如:高度,距离)与方法(如:起飞,降落)放到一个 trait 就是一个合理的拆分。

Trait 有什么优势 ?

trait 有什么优势?来看一段代码:

class User extends Model
{
    use Authenticate, SoftDeletes, Arrayable, Cacheable;

    ...
}

这个用户模型类,我们引入了四个特性:注册与授权、软删除、数组式操作、可缓存。

我们看到代码的时候一眼便知道当前支持了哪些个特性。再看下面另外一种写法:

abstract AdvansedUser {
  // ... 实现了 Authenticate, SoftDeletes, Arrayable, Cacheable 的所有方法
}
class User extends AdvansedUser
{
    ...
}

你不得不再去阅读 AdvansedUser 的代码才能理解。你想说没有可读性是因为我基类的名称没起好?可是,这种各种特性组合的一个基类是根本无法起一个见名知义的名称的,不信你可以试一下。

就算你真的起了一个见名知义的名称:AuthenticateCacheableAndArrayableSoftDeletesUser,可是当需求变更,要求在 FooUser(同样继承了这个基类) 中去除缓存特性,而 User 类保留这个特性,怎么办?再创建一个基类么?

这就是我理解的 Trait:

它不仅仅是可复用代码段的集合,它应该是一组描述了某个特性的的属性与方法的集合。它的优点在于随意组合,耦合性低,可读性高。

平常写代码的时候也许怎么拆分才是大家的痛点,分享以下几个技巧:

  • 从需求或功能描述拆分,而不是写了两段代码发现代码一样就提到一起;
  • 拆分时某些属性也一起带走,比如上面第一个例子里的价格,它是“可卖性”必备的属性;
  • 拆分时如果给 Trait 起名困难时,请认真思考你是否真的拆分对了,因为正确的拆分是很容易描述 “它是一个具有什么功能的特性” 的;

总之一定要记住:不要为了让两段相同的代码提到一起这样简单粗暴的方式来拆分。

以上是个人见解,欢迎各位讨论。??

from:http://overtrue.me/articles/2016/04/about-php-trait.html

时间: 2024-10-14 17:11:58

什么是 Trait的相关文章

快学Scala 第二十一课 (初始化trait的抽象字段)

初始化trait的抽象字段: trait Logged { println("Logged constructor") def log(msg: String){ println("Logged")} } trait FileLogger extends Logged { var filename: String override def log(msg: String) { println("filename:" + filename) } }

快学Scala 第十九课 (trait的abstract override使用)

trait的abstract override使用: 当我看到abstract override介绍的时候也是一脸懵逼,因为快学scala,只介绍了因为TimestampLogger中调用的super.log依旧是个abstract class,所以必须在方法前加上abstract和override.但是并没有具体介绍如何使用,然后查阅了其他文档,才明白使用方法. 下面的代码定义了超类LoggerEmpty,这个定义意味着该特质只能混入扩展LoggerEmpty的类中. 在特质中声明抽象方法中有

快学Scala 第十八课 (trait多继承)

trait多继承: trait的继承并不像类拥有相同的含义!在下面这个例子中,如果还是运用类的继承的思想,那么运行结果将是什么也没有. trait Logged { def log(msg: String){ } } trait ConsoleLogger extends Logged { override def log(msg: String){ super.log(msg) } } 但是事实并非如此: trait Logged { def log(msg: String){ println

scala中trait学习笔记

scala中提供的trait(特质)和Java中的Interface有很多相似之处.都可以持有方法的声明和属性,但是trait还有比interface强大的多的其他用法. 1. trait可以带有方法实现: 2. trait与interface一样,可以互相继承.但是trait可以继承自某个类,但是这种特质只能够混入父类的子类中,不能随意混入: 3. trait中可以在运行时动态调用方法. 下面举一个trait使用的例子. 首先定义一个虚类IntQueue和特质Logger abstract c

scala class和object,trait的区别

Scala类 1 2 3 4 5 6 7 8 9 10 11 12 13 class Counter {   private var value = 0 // 必须初始化字段   def increment() { value += 1 } // 方法默认公有   def current = value // 调用必须是myCounter.current这种风格 } class Student{    var age=20     //底层编译器会自动为私有的age添加get和set的公有方法,

Scala trait

trait logger{ def log(msg : String)} //继承的第一个trait用extends关键字,其余的用with 连接 class ConcreteLogger extends logger with Cloneable{ override def log(msg : String) = { println("log log") }} //AnotherLogger一定要继承logger,否则不能混入 trait AnotherLogger extends

初学scala4——trait混入

最近在调优程序,总要对比程序执行的时间,之前都是在程序片段前后加上时间然后相减. 今天看了别人写的代码,使用了trait混入,减少了很多同样代码,mark一下,也加深对trait混入的理解. trait TMetrics { def timeIt[T](desc: String)(f: () => T): T = { println(s"Thread:${Thread.currentThread().getId} | BEGIN | ${desc} | ${new Date()}"

从大数据菜鸟走上大师的历程 Scala 第十二讲 trait

trait 类似于Java 中的Interface 但有差别 trait可以继承trait并且在trait 可以写抽象的方法,也可以具体实现方法 实例如下 trait Walk { def walk(){} } class person extends Walk {   println("hello -----")   override def walk(){println("Let's have a walk ")} } trait Run extends Wal

scala学习笔记-面向对象编程之Trait(13)

将trait作为接口使用 1 // Scala中的Triat是一种特殊的概念 2 // 首先我们可以将Trait作为接口来使用,此时的Triat就与Java中的接口非常类似 3 // 在triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可 4 // 类可以使用extends关键字继承trait,注意,这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是trait,统一都是extends 5 // 类继承tra

Size differences of Arctic marine protists between two climate periods—using the paleoecological record to assess the importance of within-species trait variation

ecology and evolution ,个人觉得这个期刊点数虽不高,但属于主流期刊. 文献:Mousing, E. A. et al. Size differences of Arctic marine protists between two climate periods—using the paleoecological record to assess the importance of within-species trait variation. Ecol Evol 7, 3–