Почему PHP Trait не может реализовывать интерфейсы?

83

Мне интересно, почему PHP Trait (PHP 5.4) не может реализовывать интерфейсы.

Обновление от user1460043 answer => ... не может требовать класс, который использует его для реализации определенного интерфейса

Я понимаю, что это могло быть очевидным, потому что люди могли подумать, что если a Class Aиспользует a, Trait Tкоторый реализует a interface I, то он Class Aдолжен реализовывать a не interface Iнапрямую (и это неверно, потому что Class Aможно переименовать методы черт).

В моем случае моя черта - это вызов методов из интерфейса, который реализует класс, использующий эту черту.

Эта черта фактически является реализацией некоторых методов интерфейса. Итак, я хочу «спроектировать» код, чтобы каждый класс, который хочет использовать мою черту, должен реализовывать интерфейс. Это позволит Trait использовать методы класса, определенные интерфейсом, и быть уверенным, что они существуют в классе.

Лето
источник
13
Дело не в этом, я знаю разницу между трейтами и интерфейсами.
Leto
1
Возможно, существует техническая причина, но мне интересно, зачем вам это нужно? Вы не можете создать экземпляр признака, поэтому его реализация интерфейса не дает вам никаких преимуществ при вводе текста. Если вы хотите, чтобы это, как вы говорите, заставило классы, использующие эту черту, реализовать интерфейс, тогда вы задаетесь вопросом, будет ли (абстрактный) базовый класс более подходящим.
Вы правы, я мог бы использовать абстрактные классы везде, но я обновляю свой код до Trait, и это позволяет избежать проблем, которые у меня были с простым наследованием, поэтому я использую trait. Так что, может быть, в этом случае это возможно, но в некоторых других нет.
Leto
2
Или, может быть, проще: почему в PHP нет типов Traits?
nnevala

Ответы:

98

Действительно короткая версия проще, потому что вы не можете. Так работают черты характера.

Когда вы пишете 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();

Однако - если вам действительно нужно обеспечить, чтобы любой класс, который использует черту, имел определенный метод, я думаю, вы можете использовать черты там, где вы должны были быть абстрактными классами в первую очередь.

Или что у вас неверная логика. Вы должны требовать, чтобы классы, реализующие интерфейсы, имели определенные функции, а не то, что если у них есть определенные функции, они должны объявить себя реализующими интерфейс.

редактировать

Фактически вы можете определить абстрактные функции внутри 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, но многие из этих заданий имеют общие функции, которые лучше всего реализованы с помощью общего кода (например, и секретарю, и программисту нужен typeметод ). Можете ли вы представить, как это можно реализовать без черт?
scragar
@scragar, вы должны спросить об этом на сайте programmers.stackexchange.com, но вкратце я бы объединил «Human» с несколькими «Jobs» в класс «WorkingHuman».
Danack
1
Еще один. Если интерфейс определяет некоторый контракт осведомленности, и этот контракт является общим для большинства реализаций. Но у этих реализаций есть свой третий тип. Что-то вроде Command с ContainerAwareInterface. Но у Comand есть свои специфические области применения. Поэтому мне нужно повторяться каждый раз, когда мне нужна осведомленность о контейнерах, но если я использую Trait, я не могу определить свой собственный контракт для конкретного интерфейса. Может быть, разработчикам ядра стоит рассмотреть интерфейсы Go-Type (например, структурную типизацию)?
lazycommit
3
Это действительно странно, поскольку на самом деле мы с коллегами используем трейты только тогда, когда хотим поделиться кодом, который требуется в нескольких классах, реализующих интерфейс, но исходящих от разных предков. также нет разумного объяснения, почему компилятор может изменять код внутри класса, но не интерфейсы, реализованные этим классом. ... это просто "отсутствующая" функция ... "потому что вы не можете" объясняет это лучше всего
Summer-Sky
5
Я считаю, что разработчики ядра PHP должны немного изучить Scala, где черты считаются полноценными типами ... Мне грустно, что PHP постепенно хочет улучшить свою систему типизации, но не принимает во внимание существующие хорошо работающие реализации
Винсент Пазеллер
28

Есть RFC: Traits with interfaces предлагает добавить в язык следующее:

trait SearchItem implements SearchItemInterface
{
    ...
}

Методы, требуемые интерфейсом, могут быть либо реализованы с помощью признака, либо объявлены как абстрактные, и в этом случае ожидается, что класс, использующий признак, реализует его.

Эта функция в настоящее время не поддерживается языком, но находится на рассмотрении (текущий статус RFC: в стадии обсуждения ).

Илия
источник
Я полагаю, что в случае подтверждения люди захотят, чтобы все больше и больше функций из обычных классов было реализовано в чертах. До тех пор, пока между ними не будет разницы, и у нас будет какая-то черта Франкенштейна, которая не разделяет должным образом заботы и обязанности. Как подчеркивается в лучшем ответе, черты следует рассматривать как удобство копирования; не должны слишком сильно пересекать границы классов. Мы хотим, чтобы класс реализовал интерфейс, независимо от того, идет ли реализация из прямого кода или из использования признака; возможность реализовать интерфейсы в чертах может сбивать с толку и вводить в заблуждение
Kamafeather
Одно из прекрасных применений трейтов - это простая для вставки реализация интерфейса по умолчанию. Если вы хотите убедиться, что черта соответствует интерфейсу, было бы неплохо, если бы компиляторы помогли
Могучий Крис
Это предложение не делает так, чтобы класс, использующий признак, автоматически реализовывал эти интерфейсы признаков (это не сработает, поскольку вы можете переименовывать / заменять методы признаков). Аргумент скользкой дорожки о том, что принятие этого
Могучий Крис
Я думаю, должны существовать только трейты, ни интерфейсы, ни абстрактные классы. И черты характера должны быть типами языка.
Давид Биро
10

[...] для "разработки" в коде того, что каждый класс, который хочет использовать мою черту, должен реализовывать интерфейс. Это позволит Trait использовать методы класса, определенные интерфейсом, и быть уверенным, что они существуют в классе.

Это звучит очень разумно, и я бы не сказал, что с вашим дизайном что-то не так. С учетом этой идеи были предложены черты, см. Второй пункт здесь:

  • Типаж предоставляет набор методов, реализующих поведение.
  • Для характеристики требуется набор методов, которые служат параметрами для предоставленного поведения.
  • [...]

Шерли и др., Черты: составные единицы поведения, ECOOP'2003, LNCS 2743, стр. 248–274, Springer Verlag, 2003, стр. 2

Так что, возможно, было бы правильнее сказать, что вы хотите, чтобы черта требовала интерфейса, а не «реализовывала» его.

Я не вижу причин, по которым эта черта «требует (для реализации ее потребительских классов) интерфейсной функции» в PHP должна быть невозможной, но в настоящее время она, похоже, отсутствует.

Как отмечает @Danack в своем ответе , вы можете использовать абстрактные функции в трейте, чтобы «требовать» их от классов, которые используют трейт. К сожалению, вы не можете этого сделать с частными функциями .

user1460043
источник
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.
    }
}

Прошу прощения за мой английский

NekoOs
источник