Dlaczego PHP Trait nie może implementować interfejsów?

83

Zastanawiam się, dlaczego PHP Trait (PHP 5.4) nie może implementować interfejsów.

Aktualizacja z answer => ... użytkownika1460043 nie może wymagać klasy, która używa jej do implementacji określonego interfejsu

Rozumiem, że może to być oczywiste, ponieważ ludzie mogą pomyśleć, że jeśli a Class Aużywa a, Trait Tktóre implementuje an interface I, to Class Apowinno implementować się pośrednio interface I(i to nie jest prawda, ponieważ Class Amożna zmienić nazwę metod cech).

W moim przypadku cechą jest wywoływanie metod z interfejsu, które implementuje klasa używająca cechy.

Cecha jest tak naprawdę implementacją niektórych metod interfejsu. Tak więc chcę „zaprojektować” w kodzie, że każda klasa, która chce używać mojej cechy, musi zaimplementować interfejs. Umożliwiłoby to Trait użycie metod klas zdefiniowanych przez interfejs i upewnienie się, że istnieją w klasie.

Leto
źródło
13
Nie o to chodzi, znam różnicę między cechami a interfejsami.
Leto
1
Być może jest powód techniczny, ale zastanawiam się, dlaczego miałbyś chcieć? Nie możesz utworzyć instancji cechy, więc implementacja interfejsu nie daje żadnych korzyści związanych z podpowiadaniem typu. Jeśli chcesz, aby, jak powiedziałeś, wymusić na klasach, które używają tej cechy, implementacji interfejsu, to zastanawiasz się, czy (abstrakcyjna) klasa bazowa byłaby bardziej odpowiednia.
Masz rację, mogę używać klas abstrakcyjnych wszędzie, ale aktualizuję swój kod do Trait i unikam problemów, które miałem z prostym dziedziczeniem, dlatego używam trait. Więc może w takim przypadku jest to możliwe, ale w innych nie.
Leto
2
A może prościej: dlaczego w PHP nie ma typów cech?
nnevala

Odpowiedzi:

97

Naprawdę krótka wersja jest prostsza, ponieważ nie możesz. Nie tak działają cechy.

Kiedy piszesz use SomeTrait;w PHP, mówisz (efektywnie) kompilatorowi, aby skopiował i wkleił kod z cechy do klasy, w której jest używany.

Ponieważ element use SomeTrait;znajduje się wewnątrz klasy, nie można go dodawać implements SomeInterfacedo klasy, ponieważ musi znajdować się poza klasą.

„Dlaczego w PHP nie ma typów cech?”

Ponieważ nie można ich utworzyć. Cechy są tak naprawdę konstrukcją językową (nakazującą kompilatorowi skopiowanie i wklejenie kodu cechy do tej klasy) w przeciwieństwie do obiektu lub typu, do którego może się odwoływać Twój kod.

Tak więc chcę „zaprojektować” w kodzie, że każda klasa, która chce używać mojej cechy, musi zaimplementować interfejs.

Można to wymusić, używając klasy abstrakcyjnej do usecechy, a następnie rozszerzając z niej klasy.

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

Jednak - jeśli musisz wymusić, że jakakolwiek klasa, która używa cechy, ma określoną metodę, myślę, że możesz używać cech, w których powinieneś był przede wszystkim klasami abstrakcyjnymi.

Albo że masz złą logikę. Masz wymagać, aby klasy, które implementują interfejsy, miały określone funkcje, a nie to, że jeśli mają pewne funkcje, muszą zadeklarować się jako implementujące interfejs.

Edytować

Właściwie możesz zdefiniować abstrakcyjne funkcje wewnątrz Traits, aby wymusić na klasie implementację metody. na przykład

trait LoggerTrait {

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

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

Jednak to nadal nie pozwala na zaimplementowanie interfejsu w tej funkcji i nadal pachnie jak zły projekt, ponieważ interfejsy są znacznie lepsze niż cechy w definiowaniu kontraktu, który klasa musi spełnić.

Danack
źródło
2
Jak byś wtedy zasugerował, żeby to rozłożyć? Mam klasę Human, ta klasa jest wyabstrahowana na podklasy w oparciu o Job, ale wiele z tych zadań ma takie same cechy, które najlepiej zaimplementować w kodzie współdzielonym (na przykład zarówno sekretarz jak i programista potrzebują typemetody ). Czy możesz pomyśleć, jak można to zaimplementować bez cech?
scragar
@scragar, powinieneś o to zapytać na programmers.stackexchange.com, ale krótka wersja jest taka, że ​​złożyłbym „Human” z wieloma „Jobs” na klasę „WorkingHuman”.
Danack
1
Jeszcze jeden. Jeśli interfejs definiuje jakiś kontrakt świadomościowy i ten kontrakt jest wspólny dla większości wdrożeń. Ale te implementacje mają swój własny typ trzeci. Coś w rodzaju Command z ContainerAwareInterface. Ale Comand ma swoje specyficzne obszary zastosowań. Muszę więc powtarzać się za każdym razem, gdy potrzebuję świadomości kontenera, ale jeśli używam cechy, nie mogę zdefiniować własnej umowy dla konkretnego interfejsu. Może główni programiści powinni rozważyć interfejsy Go-Type (np. Typowanie strukturalne)?
lazycommit
3
to naprawdę dziwne, ponieważ tak naprawdę moi koledzy i ja używamy cech tylko wtedy, gdy chcemy udostępniać kod, który jest wymagany w kilku klasach implementujących interfejs, ale pochodzi od różnych przodków. nie ma również rozsądnego wyjaśnienia, dlaczego kompilator może zmienić kod wewnątrz klasy, ale nie interfejsów implementowanych przez tę klasę. … to po prostu „brakująca” funkcja… „ponieważ nie możesz” wyjaśnia to najlepiej
Summer-Sky
5
Uważam, że rdzenni programiści PHP powinni przestudiować trochę Scali, w której cechy są uważane za pełnoprawne typy ... Jest mi smutno, że PHP stopniowo chce ulepszać swój system pisania, ale nie bierze pod uwagę istniejących dobrze działających implementacji
Vincent Pazeller
28

Jest RFC: Traits with interfaces sugeruje dodanie do języka:

trait SearchItem implements SearchItemInterface
{
    ...
}

Metody wymagane przez interfejs mogą być implementowane przez cechę lub zadeklarowane jako abstrakcyjne, w takim przypadku oczekuje się, że klasa, która używa cechy, implementuje ją.

Ta funkcja nie jest obecnie obsługiwana przez język, ale jest rozważana (obecny stan RFC to: W trakcie dyskusji ).

Ilija
źródło
Przypuszczam, że jeśli zostanie to potwierdzone, ludzie będą chcieli, aby coraz więcej funkcji z normalnych klas było implementowanych do cech. Dopóki nie będzie między nimi żadnej różnicy i będziemy mieli jakąś cechę Frankensteina, która nie rozdziela prawidłowo obaw i obowiązków. Jak podkreśla najlepsza odpowiedź, cechy należy postrzegać jako wygodę kopiowania przeszłości; nie powinien zbytnio przekraczać granic klas. Chcemy, aby klasa implementowała interfejs, niezależnie od tego, czy implementacja pochodzi z bezpośredniego kodu, czy z funkcji using; pozwalają na implementację interfejsów do cech mogą być mylące i wprowadzające w błąd
Kamafeather
Jednym ze świetnych zastosowań cech jest zapewnienie łatwej do wklejenia domyślnej implementacji interfejsu. Jeśli chcesz mieć pewność, że cecha spełnia interfejs, dobrze byłoby poprosić kompilatorów o pomoc
The Mighty Chris
Ta propozycja nie sprawia, że ​​klasa używająca cechy automatycznie implementuje interfejsy tych cech (co nie zadziała, ponieważ można zmienić nazwę / zamienić metody cech). Argument o śliskim zboczu, że przejście tego w jakiś sposób przejdzie przez bardziej agresywne przyszłe RFC, nie powinien powstrzymywać wody
The Mighty Chris
Myślę, że powinny istnieć tylko cechy, ani interfejsy, ani klasy abstrakcyjne. A cechy powinny być typami w języku.
Dávid Bíró,
10

[...] „zaprojektować” w kodzie, że każda klasa, która chce używać mojej cechy, musi zaimplementować interfejs. Umożliwiłoby to Trait użycie metod klas zdefiniowanych przez interfejs i upewnienie się, że istnieją w klasie.

Brzmi to bardzo rozsądnie i nie powiedziałbym, że z twoim projektem musi być coś złego. Cechy zostały zasugerowane z myślą o tym pomyśle, zobacz drugi punkt tutaj:

  • Cecha zapewnia zestaw metod implementujących zachowanie.
  • Cecha wymaga zestawu metod, które służą jako parametry zapewnianego zachowania.
  • […]

Schärli i in., Traits: Composable Units of Behavior, ECOOP'2003, LNCS 2743, str. 248–274, Springer Verlag, 2003, str. 2

Więc może lepiej byłoby powiedzieć, że chcesz, aby cecha wymagała interfejsu, a nie „implementowała” go.

Nie widzę powodu, dla którego byłoby niemożliwe, aby ta cecha wymagała (jej klas konsumenckich do zaimplementowania) funkcji interfejsu w PHP, ale obecnie wydaje się jej brakować.

Jak zauważa @Danack w swojej odpowiedzi , możesz użyć funkcji abstrakcyjnych w cechy, aby „wymagać” ich od klas, które ją używają. Niestety nie możesz tego zrobić z prywatnymi funkcjami .

user1460043
źródło
1

Zgadzam się z odpowiedzią @Danack, jednak trochę ją uzupełnię.

Naprawdę krótka wersja jest prostsza, ponieważ nie możesz. Nie tak działają cechy.

Przychodzi mi do głowy tylko kilka przypadków, w których to, o co prosisz, jest konieczne i jest bardziej widoczne jako problem projektowy niż jako błąd języka. Wyobraź sobie, że jest taki interfejs:

interface Weaponize
{
    public function hasAmmunition();
    public function pullTrigger();
    public function fire();
    public function recharge();
}

Utworzono cechę , która implementuje jedną z funkcji zdefiniowanych w interfejsie, ale w procesie wykorzystuje inne funkcje również zdefiniowane przez interfejs , podatne na błąd, że jeśli klasa, która używa tej funkcji, nie implementuje interfejsu, wszystko nie może pobrać cyngiel

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

class Warrior
{
    use Triggerable;
}

Łatwym rozwiązaniem jest po prostu wymuszenie na klasie, która używa cechy, aby zaimplementowała również te funkcje:

trait Triggerable
{
    public abstract function hasAmmunition();
    public abstract function fire();

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

Czyli cecha nie jest całkowicie zależna od interfejsu , ale propozycja implementacji jednej z jej funkcji, ponieważ używając cechy klasa będzie wymagała implementacji metod abstrakcyjnych.

Ostateczny projekt

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

Proszę wybaczyć mój angielski

NekoOs
źródło