为啥 PHP Trait 不能实现接口?

Posted

技术标签:

【中文标题】为啥 PHP Trait 不能实现接口?【英文标题】:Why PHP Trait can't implement interfaces?为什么 PHP Trait 不能实现接口? 【发布时间】:2013-01-17 22:14:56 【问题描述】:

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

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

我知道这可能很明显,因为人们可能会认为,如果 Class A 正在使用正在实现 interface ITrait T,那么 Class A 应该间接地实现 interface I(并且这不是真的,因为Class A 可以重命名特征方法)。

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

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

【问题讨论】:

见***.com/questions/9205083/php-traits-vs-interfaces 这不是重点,我知道特质和接口的区别。 也许有技术原因,但我想知道您为什么要这样做?你不能实例化一个特征,所以让它实现一个接口不会给你任何类型提示的好处。如您所说,如果您希望强制使用该特征的类实现接口,那么您想知道(抽象)基类是否更合适。 你说得对,我可以在任何地方使用抽象类,但我正在将我的代码更新为 Trait,它避免了我在简单继承时遇到的问题,这就是我使用 trait 的原因。所以也许在那种情况下是可能的,但在其他一些情况下则不是。 或者更简单地说:为什么 PHP 中没有 Traits 类型? 【参考方案1】:

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

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

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

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

因为它们不能被实例化。 Traits 实际上只是 language construct(告诉编译器将 trait 代码复制并粘贴到此类中),而不是您的代码可以引用的对象或类型。

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

这可以使用抽象类来强制执行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 的任何类都具有特定的方法,我认为您可能在最初应该是抽象类的地方使用了 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 好得多。

【讨论】:

那么你建议如何布置这个,我有一个 Human 类,这个类被抽象为基于 Job 的子类,但是其中许多作业共享最好用共享代码实现的功能(例如秘书和程序员都需要type 方法)。你能想到如何在没有特征的情况下实现这一点吗? @scragar 你应该在programmers.stackexchange.com 询问这个问题,但简短的版本是我会将“人类”与多个“工作”组合成一个“工作人类”类。 还有一个。如果接口定义了一些感知契约,并且该契约对于大多数实现来说是常见的。但是这些实现有它们自己的类型三。类似于带有 ContainerAwareInterface 的命令。但 Comand 有自己特定的使用领域。所以我每次需要容器意识时都需要重复自己,但如果我使用 Trait,我无法为特定接口定义自己的合同。也许核心开发者应该考虑 Go-Type 接口(例如结构类型)? 这真的很奇怪,因为实际上我和我的同事只在我们想要共享实现接口但来自不同祖先的多个类中所需的代码时才使用特征。也没有合理的解释为什么编译器可以改变类内部的代码,但不能改变这个类实现的接口。 ......这只是一个“缺失”的功能......“因为你不能”最好地解释了这一点 我认为 PHP 核心开发人员应该学习一点 Scala,其中 Trait 被认为是成熟的类型......我很遗憾 PHP 逐渐想要改进其类型系统但没有考虑到考虑现有运行良好的实施【参考方案2】:

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

trait SearchItem implements SearchItemInterface

    ...

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

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

【讨论】:

我想,如果得到确认,那么人们会希望越来越多的普通类的特性被实现到特征中。直到它们之间没有区别,并且我们将拥有某种无法正确划分关注点和责任的科学怪人特征。正如最佳答案所强调的那样,特征应被视为复制过去的便利;不应该过多地跨越类的界限。我们希望一个类实现一个接口,无论实现是来自直接代码还是来自使用 trait;允许在特征中实现接口可能会令人困惑和误导 特征的一个很好的应用是提供一个易于粘贴的默认接口实现。如果你想确保一个 trait 满足一个接口,编译器的帮助会很好 这个提议没有使得使用特征的类会自动实现这些特征接口(因为您可以重命名/替换特征方法,所以这是行不通的)。滑坡的论点认为通过这个会以某种方式通过更激进的未来 RFC 不应该站得住脚 我认为,应该只存在特征,不应该存在接口,也不应该存在抽象类。并且特征应该是语言中的类型。【参考方案3】:

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

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

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

Schärli et al, Traits: Composable Units of Behaviour, ECOOP’2003, LNCS 2743, pp. 248–274, Springer Verlag, 2003, Page 2

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

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

正如@Danack 在他的answer 中指出的那样,您可以在特征中使用抽象函数从使用该特征的类中“要求”它们。不幸的是,private functions 无法做到这一点。

【讨论】:

【参考方案4】:

我同意@Danack 的回复,不过我会补充一点。

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

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

interface Weaponize

    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();

已经创建了一个 trait,它实现了 interface 中定义的功能之一,但在此过程中使用了 interface 中定义的其他功能>,容易出现如果使用该特性的类没有实现接口一切都无法触发的错误

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.
    


请原谅我的英语

【讨论】:

以上是关于为啥 PHP Trait 不能实现接口?的主要内容,如果未能解决你的问题,请参考以下文章

PHP 进阶之 抽象类(abstract)接口(interface)Trait(特征)

php Trait的使用

PHP的Trait机制

现代 PHP 新特性系列 —— Trait 概览

PHP 中 Trait 和抽象类的区别

转PHP的Trait 特性