为什么PHP trait不能实现接口?

94
我想知道为什么PHP Trait(PHP 5.4)不能实现接口。
更新来自user1460043的回答 => ...不能要求使用它的类来实现特定的接口
我明白这可能是显而易见的,因为人们可能会认为如果一个Class A使用了一个实现了接口I的Trait T,那么Class A应该间接地实现接口I(这是不正确的,因为Class A可以重命名Trait方法)。
在我的情况下,我的Trait调用了使用Trait的类实现的接口的方法。
实际上,Trait是接口的一些方法的实现。 因此,我希望在代码中“设计”,每个想要使用我的Trait的类都必须实现该接口。这将允许Trait使用由接口定义的类方法,并确保它们存在于类中。

请参见 https://dev59.com/2Gox5IYBdhLWcg3wcj13。 - Mihai8
19
不是这个意思,我知道特征和接口的区别。 - Leto
1
也许有技术原因,但我想知道为什么要这样做?你不能实例化一个trait,所以让它实现一个接口并不能给你任何类型提示的好处。如果你想要这个,就像你说的那样,强制使用trait的类来实现一个接口,那么你必须考虑一个(抽象)基类是否更合适。 - user1914530
1
你说得对,我可以在任何地方使用抽象类,但我正在将我的代码更新为Trait,并避免了我在简单继承中遇到的问题,这就是为什么我使用Trait的原因。所以也许在某些情况下可能是可以的,但在其他情况下可能不行。 - Leto
2
或者用更简单的说法:为什么 PHP 中没有特征类型? - nnevala
显示剩余2条评论
4个回答

117

简单来说,由于 Traits 的工作方式如此,所以真正的短版本会更简单。

在 PHP 中写 use SomeTrait; 就像(实际上)告诉编译器将 Trait 中的代码复制并粘贴到正在使用它的类中。

因为 use SomeTrait; 在类内部,所以它无法向类添加 implements SomeInterface,因为这必须在类外面。

"为什么 Traits 不是 PHP 中的类型? "

因为它们无法被实例化。Traits 只是一个语言结构(告诉编译器将该 trait 代码复制并粘贴到这个类中), 而不是您的代码可以引用的对象或类型。

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

可以使用抽象类强制执行这一点,通过在其中使用 trait 并从其中扩展类。

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内定义抽象函数以强制类实现该方法。例如:

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更好地定义了类需要满足的合同。


2
你认为应该如何布局呢?我有一个Human类,这个类被抽象成基于职业的子类,但是许多职业共享最好使用共享代码实现的特性(例如秘书和程序员都需要“类型”方法)。你能想到如何在没有traits的情况下实现吗? - scragar
@scragar,这个问题你应该去 http://programmers.stackexchange.com/ 上问问,简短的答案是我会将“人类”和多个“工作”组合成一个“工作中的人类”类。 - Danack
1
再说一遍。如果接口定义了某些意识契约,并且该契约适用于大多数实现。但是这些实现有自己的类型三。类似具有ContainerAwareInterface的Command。但是Command具有其自己特定的使用领域。因此,每次需要容器感知时,我都需要重复自己,但是如果我使用Trait,则无法为特定接口定义其自己的契约。也许核心开发人员应该考虑Go-Type接口(例如结构化类型)? - lazycommit
3
这很奇怪,因为我的同事和我只在想要共享需要在实现不同祖先的多个类中使用的代码时才使用traits。而且没有合理的解释为什么编译器可以修改类内部的代码,但不能修改该类所实现的接口。...这只是一个“缺失”的功能...“因为你不能”最好解释了这一点。 - Summer-Sky
5
我认为 PHP 核心开发者应该学一点 Scala,因为其中 traits 被视为完全成熟的类型。我觉得 PHP 逐渐想要改进其类型系统,但不考虑现有已运作良好的实现,这让我感到遗憾。 - Vincent Pazeller
显示剩余3条评论

31

有一个RFC:具有接口的Traits建议将以下内容添加到语言中:

trait SearchItem implements SearchItemInterface
{
    ...
}

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

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


我认为,如果得到确认,人们会希望将正常类中的更多功能实现到traits中。直到它们之间没有区别,我们会有一种无法适当地分割关注点和责任的弗兰肯斯坦式traits。正如最好的答案所强调的那样,traits应该被视为复制粘贴的便利性;不应该过多地跨越类的边界。我们希望一个类实现一个接口,无论是直接从代码中实现还是使用trait实现;在trait中实现接口可能会令人困惑和误导。 - Kamafeather
1
特征的一个伟大应用是提供一个易于粘贴的接口默认实现。如果您想确保特征满足接口,编译器的帮助会很好。 - The Mighty Chris
1
这个提案并不意味着使用 trait 的类会自动实现这些 trait 接口(因为您可以重命名/替换 trait 方法,所以这样做是行不通的)。通过这个提案会导致更多激进的未来 RFCs 获得通过的“滑坡论”不应该成立。 - The Mighty Chris
我认为,只应该存在特质(traits),而不是接口(interfaces)或抽象类(abstract classes)。并且特质应该成为语言中的类型。 - Dávid Bíró

11
[...]在代码中“设计”每个想要使用我的特质的类都必须实现接口。这将允许Trait使用由接口定义的类方法,并确保它们存在于类中。
这听起来非常合理,我不认为你的设计有任何问题。Trait已经提出了这个想法,参见这里的第二点:
- Trait提供了一组实现行为的方法。 - Trait需要一组作为提供的行为参数的方法。 - [...] Schärli et al, Traits: Composable Units of Behaviour, ECOOP’2003, LNCS 2743, pp. 248–274, Springer Verlag, 2003, Page 2 因此,也许更恰当的说法是你想要一个Trait来要求一个接口,而不是“实现”它。
我认为在PHP中实现“特征需要(其使用类实现)接口”的功能是可行的,但目前似乎缺少这个功能。
正如@Danack在他的答案中指出的那样,您可以在trait中使用抽象函数来从使用该trait的类中“要求”它们。不幸的是,您无法对私有函数执行此操作。

5

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

真正简短的版本更为简单,因为你无法这样做。这不是Trait的工作方式。

我只能想到少数几种情况下需要您所请求的内容,并且更明显地表现为设计问题而不是语言问题。想象一下有一个像这样的接口:

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

Please excuse my English


从理论上讲,使用接口但没有实现其所有方法的特征将被要求将其余方法声明为抽象方法,以便使用该特征的类必须这样做。这类似于类没有实现所有接口方法的情况:它必须是抽象的或声明这些方法(具体或抽象)。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接