为什么PHP Trait无法实现接口?


82

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

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

我明白,这可能是显而易见的,因为人们可能会觉得,如果Class A是使用Trait T其正在实施的interface I,比Class A应执行interface Iundirectly(这是不正确的,因为Class A可以重命名特征的方法)。

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

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



13
这不是重点,我知道特征和接口之间的区别。
Leto

1
也许有技术原因,但我想知道为什么要这么做?您无法实例化特征,因此让其实现接口不会给您带来任何打字提示的好处。就像您说的那样,如果您要强制使用该特性的类实现接口,那么您想知道(抽象的)基类是否更合适。

没错,我可以在任何地方使用抽象类,但是我将代码更新为Trait,并且它避免了我在使用简单继承时遇到的问题,这就是为什么我使用trait的原因。因此,在这种情况下也许是可行的,但在另一些情况下则没有。
Leto

2
或更简单地说:为什么PHP中没有Traits类型?
nnevala 2013年

Answers:


97

真正简短的版本比较简单,因为您做不到。这不是特质的工作方式。

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,而本来应该是抽象类。

还是您的逻辑有误。您的意思是要求实现接口的类具有某些功能,而不是要求它们具有某些必须将自身声明为实现接口的功能。

编辑

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

trait LoggerTrait {

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

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

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


2
那么,您有什么建议呢?我有一个Human类,该类基于Job被抽象为子类,但是其中许多Job具有共享功能,最好通过共享代码实现(例如,秘书和程序员都需要该type方法) )。您能想到没有特质的情况如何实现吗?
scragar's

@scragar,您应该在programmers.stackexchange.com上问这个问题,但简短的版本是我将“ Human”与多个“ Jobs”组合为“ WorkingHuman”类。
Danack 2014年

1
多一个。如果接口定义了一些意识契约,那么该契约对于大多数实现都是通用的。但是这些实现都有自己的第三种类型。类似于Command与ContainerAwareInterface。但是Comand拥有自己的特定使用范围。因此,每次需要容器意识时,我都需要重复自己的步骤,但是如果使用Trait,则无法为特定接口定义其自己的合同。也许核心开发人员应该考虑使用Go-Type接口(例如,结构化键入)?
lazycommit 2015年

3
这真的很奇怪,因为实际上我和我的同事们只在我们要共享实现接口的几个类中所需的代码时才使用特征,但是这些代码来自不同的祖先。也没有合理的解释,为什么编译器可以更改类中的代码,但不能更改此类实现的接口。...这只是一个“缺失”功能...“因为你不能”解释了最好的情况
Summer-Sky

5
我相信PHP核心开发人员应该研究一些Scala,在这些特征中,特征被认为是成熟的类型...我感到很难过,PHP逐渐希望改进其类型系统,但没有考虑到现有的运行良好的实现
Vincent Pazeller

28

有一个RFC:具有接口的特性建议在语言中添加以下内容:

trait SearchItem implements SearchItemInterface
{
    ...
}

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

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


我猜想,如果得到证实,那么人们将希望越来越多的普通类别的特征被实施为特征。直到它们之间没有区别,并且我们将具有某种弗兰肯斯坦特质,无法正确划分关注点和责任。最好的答案是强调,特质应被视为方便复制;不应过多地跨越类的边界。我们想要一个类来实现一个接口,无论该实现来自直接代码还是来自使用特征。允许将接口实现为特征可能会造成混淆和误导
Kamafeather

traits的一项重要应用是提供一种易于粘贴的默认接口实现。如果你想确保满足特质的接口,将是不错的编译器的帮助
全能的克里斯

该建议没有做到这一点,因此使用trait的类将自动实现这些traits接口(该操作不起作用,因为您可以重命名/替换trait方法)。滑溜溜的论点认为,通过这一点将以某种方式通过更具侵略性的未来RFC,这不应该搁浅
The Mighty Chris

10

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

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

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

Schärli等人,《特征:行为的可组合单位》,ECOOP'2003,LNCS 2743,第248-274页,施普林格出版社,2003年,第2页

因此,说您希望特征需要接口而不是“实现”接口可能更合适。

我看不出为什么不可能在PHP中具有此“特征需要(其消费者类来实现)接口”功能的原因,但目前看来似乎已经消失了。

就像@Danack在他的答案中指出的那样,您可以在trait中使用抽象函数来从使用trait的类中“获取”它们。不幸的是,您不能使用私有功能来做到这一点。


1

我同意@Danack的回复,但是我会对其加以补充。

真正简短的版本比较简单,因为您做不到。这不是特质的工作方式。

我只能想到少数情况是您需要的是必需的,而这种情况在设计上比语言上的失败更为明显。试想一下,有一个这样的接口:

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 Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

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

因此,特征并不完全依赖于接口,而是实现其功能之一的建议,因为使用特征时,类将要求实现抽象方法。

最终设计

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.
    }
}

请原谅我的英语

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.