最近我一直在努力学习PHP,我发现自己被trait缠住了。我理解横向代码重用的概念,并且不希望必然地继承抽象类。我不明白的是:使用特征和使用界面之间的关键区别是什么?

我曾试着搜索过一篇像样的博客文章或文章,解释什么时候使用其中一种或另一种,但到目前为止,我找到的例子似乎非常相似,甚至完全相同。


当前回答

我认为trait对于创建包含方法的类是有用的,这些方法可以被用作几个不同类的方法。

例如:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

您可以在任何使用此特性的类中拥有并使用此“error”方法。

class Something
{
    use Toolkit;

    public function do_something($zipcode)
    {
        if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
            return $this->error('Invalid zipcode.');
        
        // do something here
    }
}

而对于接口,你只能声明方法签名,而不能声明它的函数代码。此外,要使用接口,您需要遵循层次结构,使用实现。但特质却不是这样。

这是完全不同的!

其他回答

描述trait的一个常用比喻是:trait是带有实现的接口。

在大多数情况下,这是一种很好的思考方式,但两者之间存在一些微妙的差异。

首先,instanceof操作符不能与trait一起工作(即,trait不是一个真正的对象),因此不能使用它来查看一个类是否具有某个trait(或者查看两个本来不相关的类是否共享一个trait)。这就是他们所说的横向代码重用的结构。

现在PHP中有一些函数可以让您获得类使用的所有特征的列表,但是特征继承意味着您需要进行递归检查,以可靠地检查类在某个时刻是否具有特定的特征(PHP doco页面上有示例代码)。不过,它当然不像instanceof那样简单干净,以我之见,这个特性会让PHP变得更好。

而且,抽象类仍然是类,因此它们不能解决与多重继承相关的代码重用问题。请记住,您只能扩展一个类(真实的或抽象的),但可以实现多个接口。

我发现trait和接口非常适合一起使用来创建伪多重继承。例如:

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

这样做意味着您可以使用instanceof来确定特定的Door对象是否是Keyed的,您知道您将得到一组一致的方法,等等,并且所有的代码在所有使用KeyedTrait的类中都位于一个位置。

基本上,您可以将trait视为代码的自动“复制-粘贴”。

使用trait是很危险的,因为在执行前我们无法知道它的作用。

然而,由于缺乏遗传等限制,性状更加灵活。

trait在注入方法时很有用,它可以检查类中是否存在另一个方法或属性。这是一篇不错的文章(但是是法语,抱歉)。

对于能读法语的人来说,GNU/Linux杂志HS 54有一篇关于这个主题的文章。

接口定义了实现类必须实现的一组方法。

当trait被使用时,方法的实现也会出现——这在接口中不会发生。

这是最大的不同。

PHP RFC的水平重用:

trait是一种在单继承语言(如PHP)中代码重用的机制。Trait旨在通过允许开发人员在不同类层次结构中的几个独立类中自由地重用方法集来减少单个继承的一些限制。

主要的区别在于,对于接口,您必须在实现上述接口的每个类中定义每个方法的实际实现,因此您可以让许多类实现相同的接口但具有不同的行为,而trait只是注入到类中的代码块;另一个重要的区别是trait方法只能是类方法或静态方法,不像接口方法也可以(通常是)是实例方法。

trait只是为了代码重用。

Interface只是提供了要在类中定义的函数的签名,它可以根据程序员的判断使用。这样就为我们提供了一组类的原型。

供参考, http://www.php.net/manual/en/language.oop5.traits.php