Чому PHP Trait не може реалізувати інтерфейси?


83

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

Оновлення з відповіді користувача1460043 => ... не може вимагати класу, який використовує його для реалізації певного інтерфейсу

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

У моєму випадку моя ознака - це виклик методів з інтерфейсу, який реалізує клас, що використовує ознаку.

Риса насправді є реалізацією деяких методів інтерфейсу. Отже, я хочу "розробити" в коді, що кожен клас, який хоче використовувати мою рису, повинен реалізовувати інтерфейс. Це дозволило б Trait використовувати методи класу, визначені інтерфейсом, і переконатися, що вони існують у класі.



13
Справа не в цьому, я знаю різницю між рисами та інтерфейсами.
Лето

1
Можливо, є технічна причина, але мені цікаво, чому б ви цього хотіли? Ви не можете створити екземпляр риси, тому її реалізація інтерфейсу не дасть вам жодних переваг підбору тексту. Якщо ви хочете, щоб це, як ви говорите, змусило класи, які використовують ознаку для реалізації інтерфейсу, тоді ви задаєтеся питанням, чи був би (абстрактний) базовий клас більш підходящим.

Ви праві, я міг використовувати абстрактні класи скрізь, але я оновлюю свій код до Trait, і це дозволяє уникнути проблем, які виникали у мене з простим успадкуванням, саме тому я використовую trait. Тож, можливо, в такому випадку це можливо, а в деяких інших - ні.
Лето

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();

Однак - якщо вам потрібно домогтися того, щоб будь-який клас, який використовує 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());
}

Однак це все ще не дозволяє вам реалізувати інтерфейс в ознаці, і все одно пахне поганим дизайном, оскільки інтерфейси набагато кращі за риси при визначенні контракту, який повинен виконати клас.


2
Як би ви запропонували викласти це тоді, у мене є клас Human, цей клас абстрагується в підкласи на основі Job, але багато з цих робочих місць мають функції, які найкраще реалізуються спільним кодом (наприклад, і секретар, і програміст потребують typeметоду ). Чи можете ви подумати, як це можна було б реалізувати без рис?
scragar

@scragar, ви повинні запитати це на programmers.stackexchange.com, але коротка версія полягає в тому, що я склав би "Human" з декількома "Jobs", щоб стати класом "WorkingHuman".
Danack

1
Ще один. Якщо інтерфейс визначає якийсь контракт про обізнаність, і цей контракт є загальним для більшості реалізацій. Але ці реалізації мають свій власний тип три. Щось на зразок Command with ContainerAwareInterface. Але Comand має свої специфічні сфери використання. Тому мені потрібно повторюватись щоразу, коли мені потрібна обізнаність щодо контейнерів, але якщо я використовую Trait, я не можу визначити власний контракт для конкретного інтерфейсу. Можливо, основним розробникам варто розглянути інтерфейси Go-Type (наприклад, Structural Typing)?
lazycommit

3
це дійсно дивно, оскільки насправді ми з колегами використовуємо риси лише тоді, коли хочемо поділитися кодом, який потрібен у кількох класах, що реалізують інтерфейс, але походять від різних предків. також немає розумного пояснення, чому компілятор може змінювати код всередині класу, але не інтерфейси, реалізовані цим класом. ... це просто "відсутня" функція ... "тому що ти не можеш" пояснює це найкраще
Summer-Sky

5
Я вважаю, що основним розробникам PHP слід вивчити трохи Scala, де риси вважаються повноцінними типами ... Мені сумно, що PHP поступово хоче вдосконалити свою систему набору тексту, але не враховує існуючі добре працюючі реалізації
Vincent Pazeller

28

Існує RFC: риси з інтерфейсами пропонує додавати до мови наступне:

trait SearchItem implements SearchItemInterface
{
    ...
}

Методи, необхідні інтерфейсу, можуть бути реалізовані ознакою або оголошені як абстрактні, і в цьому випадку очікується, що клас, який використовує ознаку, реалізує її.

На даний момент ця функція не підтримується мовою, але вона розглядається (поточний статус RFC: Під обговоренням ).


Я припускаю, що у разі підтвердження люди захочуть, щоб все більше і більше функцій із звичайних класів були реалізовані в риси. Поки між ними не буде різниці, і ми матимемо якусь рису Франкенштейна, яка не розподіляє проблеми та обов'язки належним чином. Як підкреслюється найкраща відповідь, риси слід розглядати як зручність копіювання минулого; не повинні занадто перетинати межі класів. Ми хочемо, щоб клас реалізував інтерфейс, незалежно від того, реалізація походить від прямого коду або від використання ознаки; дозволити реалізовувати інтерфейси в рисах може заплутати та ввести в оману
Kamafeather

Одним із чудових застосувань ознак є реалізація інтерфейсу, яку можна легко вставити за замовчуванням. Якщо ви хочете переконатись, що риса відповідає інтерфейсу, непогано було б допомогти компіляторам
The Mighty Chris

Ця пропозиція не робить це таким чином, що клас, що використовує ознаку, автоматично реалізує ці інтерфейси ознак (це не буде працювати, оскільки ви можете перейменовувати / замінювати методи ознак). Аргумент на слизькому схилі про те, що передача цього якимось чином пройде більш агресивні майбутні RFC, не повинен тримати воду
The Mighty Chris

Я думаю, повинні існувати лише риси, ні інтерфейси, ні абстрактні класи. І риси повинні бути типами в мові.
Давид Біро,

10

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

Це звучить дуже розумно, і я б не сказав, що у вашому дизайні має бути щось погане. Риси були запропоновані з урахуванням цієї ідеї, див. Другий пункт тут:

  • Риса надає набір методів, що реалізують поведінку.
  • Риса вимагає набору методів, що служать параметрами для наданої поведінки.
  • [...]

Schärli et al., Traits: Composable Unit of Behaviour, ECOOP'2003, LNCS 2743, pp. 248–274, Springer Verlag, 2003, стор. 2

Тож, можливо, було б доречніше сказати, що ви хочете, щоб риса вимагала інтерфейсу, а не «реалізовувала» її.

Я не бачу причини, чому в PHP не повинно бути можливості "ця ознака вимагає (її споживачі повинні реалізувати) інтерфейс", але наразі вона, здається, відсутня.

Як зазначає @Danack у своїй відповіді , ви можете використовувати абстрактні функції в ознаці, щоб "вимагати" їх від класів, що використовують ознаку. На жаль, ви не можете зробити це за допомогою приватних функцій .


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

Будь ласка, вибачте мою англійську

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.