88

我想知道为什么 PHP Trait (PHP 5.4) 不能实现接口。

从 user1460043 的答案更新 => ...不能要求使用它来实现特定接口的类

我知道这可能很明显,因为人们可能认为如果 aClass A正在使用 a Trait Twhich 正在实现 an interface I,那么 theClass A应该是非直接实现的interface I(这不是真的,因为Class A可以重命名 trait 方法)。

就我而言,我的特征是从使用该特征的类实现的接口调用方法。

trait 实际上是接口某些方法的实现。所以,我想在代码中“设计”每个想要使用我的特性的类都必须实现接口。这将允许 Trait 使用接口定义的类方法,并确保它们存在于类中。

4

4 回答 4

108

真正简短的版本更简单,因为你不能。这不是 Traits 的工作方式。

当您用use SomeTrait;PHP 编写代码时,您(有效地)告诉编译器将代码从 Trait 复制并粘贴到正在使用它的类中。

因为在use SomeTrait;类内,它不能添加implements SomeInterface到类中,因为它必须在类外。

“为什么 PHP 中没有 Traits 类型?”

因为它们不能被实例化。特征实际上只是一种语言结构(告诉编译器将特征代码复制并粘贴到此类中),而不是代码可以引用的对象或类型。

所以,我想在代码中“设计”每个想要使用我的特性的类都必须实现接口。

这可以使用一个抽象类来强制执行use,然后从它扩展类。

interface SomeInterface{
    public function someInterfaceFunction();
}

trait SomeTrait {
    function sayHello(){
        echo "Hello my secret is ".static::$secret;
    }
}

abstract class AbstractClass implements SomeInterface{
    use SomeTrait;
}

class TestClass extends AbstractClass {
    static public  $secret = 12345;

    //function someInterfaceFunction(){
        //Trying to instantiate this class without this function uncommented will throw an error
        //Fatal error: Class TestClass contains 1 abstract method and must therefore be 
        //declared abstract or implement the remaining methods (SomeInterface::doSomething)
    //}
}

$test = new TestClass();

$test->sayHello();

但是 - 如果您确实需要强制使用 Trait 的任何类都具有特定的方法,我认为您可能正在使用应该首先是抽象类的特征。

或者你的逻辑错误。您的意思是要求实现接口的类具有某些功能,而不是如果它们具有某些功能,则必须将自己声明为实现接口。

编辑

实际上,您可以在 Traits 中定义抽象函数来强制类实现该方法。例如

trait LoggerTrait {

    public function debug($message, array $context = array()) {
        $this->log('debug', $message, $context);
    }

    abstract public function log($level, $message, array $context = array());
}

然而,这仍然不允许您在 trait 中实现接口,并且仍然闻起来像一个糟糕的设计,因为在定义类需要履行的契约方面,接口比 trait 好得多。

于 2013-02-14T01:13:57.753 回答
28

有一个RFC: Traits with interfaces建议将以下内容添加到语言中:

trait SearchItem implements SearchItemInterface
{
    ...
}

接口所需的方法可以由 trait 实现,也可以声明为抽象,在这种情况下,期望使用 trait 的类实现它。

该语言当前不支持此功能,但正在考虑中(RFC 的当前状态是:正在讨论中)。

于 2017-07-21T23:16:18.410 回答
10

[...] 在代码中“设计”每个想要使用我的特性的类都必须实现接口。这将允许 Trait 使用接口定义的类方法,并确保它们存在于类中。

这听起来很合理,我不会说你的设计一定有什么问题。考虑到这个想法,已经提出了特征,请参见此处的第二点:

  • 特征提供了一组实现行为的方法。
  • 特征需要一组方法作为所提供行为的参数。
  • [...]

Schärli 等人,特征:行为的可组合单位,ECOOP'2003,LNCS 2743,第 248-274 页,Springer Verlag,2003,第 2 页

所以说你想要一个特性需要一个接口,而不是“实现”它可能更合适。

我看不出为什么在 PHP 中不可能有这个“特征需要(它的消费者类来实现)接口”特性,但目前它似乎缺失了。

正如@Danack 在他的回答中指出的那样,您可以在特征中使用抽象函数来从使用该特征的类中“要求”它们。不幸的是,您不能使用私有函数来做到这一点。

于 2016-08-11T13:16:40.017 回答
3

我同意@Danack 的回应,但我会补充一点。

真正简短的版本更简单,因为你不能。这不是 Traits 的工作方式。

我只能想到少数情况,在这些情况下您的要求是必要的,并且作为设计问题比作为语言失败更明显。试想一下,有这样一个界面:

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

已经创建了一个实现接口中定义的功能之一的特征在此过程中使用了接口定义的其他功能,容易出现错误,即如果使用该功能的类未实现接口,则一切都无法提取扳机

trait Triggerable
{
    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

class Warrior
{
    use Triggerable;
}

一个简单的解决方案是简单地强制使用trait的类也实现这些功能:

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}

所以trait并不完全依赖于interface,而是实现其功能之一的提议,因为当使用trait时,类将要求实现抽象方法。

最终设计

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

    public function pullTrigger()
    {
        if ($this->hasAmmunition()) {
            $this->fire();
        }
    }
}


class Warrior implements Weaponize
{
    use Triggerable;

    public function hasAmmunition()
    {
        // TODO: Implement hasAmmunition() method.
    }

    public function fire()
    {
        // TODO: Implement fire() method.
    }

    public function recharge()
    {
        // TODO: Implement recharge() method.
    }
}

请原谅我的英语

于 2019-05-16T01:16:19.093 回答