Cechy a interfejsy

344

Ostatnio próbuję uczyć się na PHP i odkrywałem, że jestem uzależniony od cech. Rozumiem koncepcję ponownego wykorzystania kodu horyzontalnego i nie chcę dziedziczyć po klasie abstrakcyjnej. Nie rozumiem tylko: jaka jest zasadnicza różnica między używaniem cech a interfejsami?

Próbowałem znaleźć porządny post na blogu lub artykuł wyjaśniający, kiedy użyć jednego lub drugiego, ale przykłady, które do tej pory znalazłem, wydają się tak podobne, że są identyczne.

datguywhowanders
źródło
6
interfejs nie ma żadnego kodu w treści funkcji. w rzeczywistości nie mają żadnych ciał funkcyjnych.
hakre
2
Pomimo mojej bardzo pozytywnej odpowiedzi, chciałbym, aby było napisane, że ogólnie jestem anty-cechą / mixinem . Sprawdź zapis rozmowy na czacie, aby przeczytać, w jaki sposób cechy często podważają solidne praktyki OOP .
rdlowrey
2
Twierdzę przeciwnie. Pracując z PHP wiele lat wcześniej i od pojawienia się cech, myślę, że łatwo jest udowodnić ich wartość. Wystarczy przeczytać ten praktyczny przykład, który umożliwia „modelom obrazów” chodzenie i mówienie jak Imagickprzedmioty, bez zbędnego wzdęcia potrzebnego w dawnych czasach przed cechami.
szybka zmiana
Cechy i interfejs są podobne. Główną różnicą jest to, że cechy umożliwiają implementację metod, a interfejs nie.
Jan

Odpowiedzi:

239

Interfejs definiuje zestaw metod, które klasa implementująca musi wdrożyć.

Kiedy pojawia się cecha, usepojawiają się również implementacje metod - co nie zdarza się w Interface.

To jest największa różnica.

Od poziomego ponownego wykorzystania dla PHP RFC :

Cechy to mechanizm ponownego wykorzystania kodu w językach spadkowych, takich jak PHP. Cecha ma na celu zmniejszenie niektórych ograniczeń pojedynczego dziedziczenia poprzez umożliwienie twórcy swobodnego ponownego wykorzystywania zestawu metod w kilku niezależnych klasach żyjących w różnych hierarchiach klas.

Wąwóz Alec
źródło
2
@JREAM W praktyce nic. W rzeczywistości znacznie więcej.
Wąwóz Alec
79
Tyle że cechy wcale nie są interfejsami. Interfejsy to specyfikacje, które można sprawdzić. Cechy nie mogą być porównywane, dlatego są tylko implementacją. Są dokładnym przeciwieństwem interfejsów. Ta linia w RFC jest po prostu błędna ...
ircmaxell
195
Cechy to w zasadzie kopiowanie i wklejanie przy pomocy języka .
Shahid
10
To nie jest metafora. To maskuje znaczenie słowa. To jak opisanie pudełka jako powierzchni o objętości.
cleong
6
Aby rozwinąć komentarze Ircmaxella i Shadi: Możesz sprawdzić, czy obiekt implementuje interfejs (przez instanceof), i możesz upewnić się, że argument metody implementuje interfejs za pomocą podpowiedzi typu w podpisie metody. Nie można wykonać odpowiednich kontroli cech.
Brian D'Astous,
530

Ogłoszenie publiczne:

Pragnę oświadczyć, że moim zdaniem cechy są prawie zawsze zapachem kodu i należy ich unikać na korzyść kompozycji. Moim zdaniem pojedyncze dziedzictwo jest często nadużywane do tego stopnia, że ​​jest anty-wzorcem, a wielokrotne dziedziczenie tylko pogarsza ten problem. W większości przypadków będziesz lepiej obsługiwany, faworyzując kompozycję zamiast dziedziczenia (zarówno pojedynczego, jak i wielokrotnego). Jeśli nadal interesują Cię cechy i ich związek z interfejsami, czytaj dalej ...


Zacznijmy od tego, że:

Programowanie obiektowe (OOP) może być trudnym do uchwycenia paradygmatem. To, że używasz klas, nie oznacza, że ​​Twój kod jest zorientowany obiektowo (OO).

Aby napisać kod OO, musisz zrozumieć, że OOP naprawdę dotyczy możliwości twoich obiektów. Musisz pomyśleć o zajęciach w kategoriach tego, co mogą zrobić zamiast tego, co faktycznie robią . Jest to wyraźny kontrast z tradycyjnym programowaniem proceduralnym, w którym nacisk kładziony jest na zrobienie odrobiny kodu „zrób coś”.

Jeśli kod OOP dotyczy planowania i projektowania, interfejs jest schematem, a obiekt to w pełni zbudowany dom. Tymczasem cechy są po prostu sposobem na pomoc w budowie domu określonego w planie (interfejsie).

Interfejsy

Dlaczego więc powinniśmy używać interfejsów? Po prostu interfejsy sprawiają, że nasz kod jest mniej kruchy. Jeśli wątpisz w to stwierdzenie, poproś każdego, kto został zmuszony do zachowania starszego kodu, który nie został napisany przeciwko interfejsom.

Interfejs jest umową między programistą a jego kodem. Interfejs mówi: „Tak długo, jak będziesz postępować według moich zasad, możesz mnie zaimplementować w dowolny sposób i obiecuję, że nie złamię twojego innego kodu”.

Jako przykład weźmy pod uwagę rzeczywisty scenariusz (bez samochodów i widżetów):

Chcesz wdrożyć system buforowania aplikacji sieci Web, aby zmniejszyć obciążenie serwera

Zaczynasz od napisania klasy do buforowania odpowiedzi na żądania przy użyciu APC:

class ApcCacher
{
  public function fetch($key) {
    return apc_fetch($key);
  }
  public function store($key, $data) {
    return apc_store($key, $data);
  }
  public function delete($key) {
    return apc_delete($key);
  }
}

Następnie w obiekcie odpowiedzi HTTP sprawdzasz trafienie w pamięci podręcznej przed wykonaniem całej pracy w celu wygenerowania rzeczywistej odpowiedzi:

class Controller
{
  protected $req;
  protected $resp;
  protected $cacher;

  public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
    $this->req    = $req;
    $this->resp   = $resp;
    $this->cacher = $cacher;

    $this->buildResponse();
  }

  public function buildResponse() {
    if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
      $this->resp = $response;
    } else {
      // Build the response manually
    }
  }

  public function getResponse() {
    return $this->resp;
  }
}

To podejście działa świetnie. Ale może kilka tygodni później zdecydujesz, że chcesz użyć systemu pamięci podręcznej opartego na plikach zamiast APC. Teraz musisz zmienić kod kontrolera, ponieważ zaprogramowałeś kontroler do pracy z funkcjonalnością ApcCacherklasy, a nie z interfejsem, który wyraża możliwości ApcCacherklasy. Powiedzmy, że zamiast powyższego sprawiłeś, że Controllerklasa polegała na konkretnym CacherInterfacezamiast na konkretnym ApcCacher:

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)

Aby to zrobić, zdefiniuj swój interfejs w następujący sposób:

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}

Z kolei masz zarówno swoje, jak ApcCacheri nowe FileCacherklasy, CacherInterfacei programujesz swoją Controllerklasę, aby korzystała z możliwości wymaganych przez interfejs.

Ten przykład (mam nadzieję) pokazuje, w jaki sposób programowanie interfejsu pozwala zmienić wewnętrzną implementację twoich klas bez obawy, że zmiany spowodują uszkodzenie innego kodu.

Cechy

Natomiast cechy to po prostu metoda ponownego użycia kodu. Interfejsy nie powinny być traktowane jako wzajemnie wykluczająca się alternatywa dla cech. W rzeczywistości tworzenie cech, które spełniają funkcje wymagane przez interfejs, jest idealnym przykładem użycia .

Tych cech należy używać tylko wtedy, gdy wiele klas ma tę samą funkcjonalność (prawdopodobnie podyktowane tym samym interfejsem). Nie ma sensu używać cechy do zapewnienia funkcjonalności dla jednej klasy: to tylko zaciemnia to, co robi klasa, a lepszy projekt przeniósłby funkcjonalność cechy do odpowiedniej klasy.

Rozważ następującą implementację cechy:

interface Person
{
    public function greet();
    public function eat($food);
}

trait EatingTrait
{
    public function eat($food)
    {
        $this->putInMouth($food);
    }

    private function putInMouth($food)
    {
        // Digest delicious food
    }
}

class NicePerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Good day, good sir!';
    }
}

class MeanPerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Your mother was a hamster!';
    }
}

Bardziej konkretny przykład: wyobraź sobie oba FileCacher i swojeApcCacher z dyskusji interfejsu używamy tej samej metody, aby ustalić, czy pozycja pamięci podręcznej jest nieaktualna i powinna zostać usunięta (oczywiście tak nie jest w rzeczywistości, ale idź z nią). Możesz napisać cechę i pozwolić obu klasom na użycie jej do wspólnego wymagania interfejsu.

Ostatnie ostrzeżenie: uważaj, aby nie przesadzić z cechami. Często cechy są wykorzystywane jako narzędzie do kiepskiego projektowania, gdy wystarczą wyjątkowe implementacje klasy. Powinieneś ograniczyć cechy do spełnienia wymagań interfejsu dla najlepszego projektu kodu.

rdlowrey
źródło
69
Naprawdę szukałem szybkiej, prostej odpowiedzi, która została podana powyżej, ale muszę powiedzieć, że udzieliłeś doskonałej, pogłębionej odpowiedzi, która pomoże uczynić to rozróżnienie bardziej zrozumiałym dla innych, uznanie.
datguywhowanders
35
„[C] przetwarzanie cech, które spełniają możliwości wymagane przez interfejs w danej klasie, jest idealnym przypadkiem użycia”. Dokładnie: +1
Wąwóz Aleca
5
Czy uczciwie byłoby powiedzieć, że cechy w PHP są podobne do mixin w innych językach?
Eno,
5
@igorpan Dla wszystkich celów i celów powiedziałbym, że implementacja cechy PHP jest taka sama jak wielokrotne dziedziczenie. Warto zauważyć, że jeśli cecha w PHP określa właściwości statyczne, wówczas każda klasa używająca tej cechy będzie miała własną kopię właściwości statycznej. Co ważniejsze ... widząc, jak ten post jest teraz niezwykle wysoko na serwerach SERP, gdy pytam o cechy, dodam ogłoszenie usługi publicznej na górze strony. Powinieneś to przeczytać.
rdlowrey
3
+1 za szczegółowe wyjaśnienie. Pochodzę z rubinowego tła, gdzie DUŻO używa się mixin; dodając moje dwa centy, dobrą zasadę, której używamy, można przetłumaczyć na php jako „nie implementuj metod, które mutują $ this w cechy”. Zapobiega to całej masie szalonych sesji debugowania ... Mixin również NIE powinien przyjmować żadnych założeń dotyczących klasy, w której będzie mieszany (lub powinieneś to wyjaśnić i zredukować zależności do absolutnego minimum). Pod tym względem uważam twój pomysł na cechy implementujące interfejsy za przyjemne.
m_x
67

A traitjest w zasadzie implementacją PHP mixini jest zestawem metod rozszerzenia, które można dodać do dowolnej klasy poprzez dodanie trait. Metody stają się następnie częścią implementacji tej klasy, ale bez użycia dziedziczenia .

Z podręcznika PHP (moje podkreślenie):

Cechy są mechanizmem ponownego wykorzystania kodu w językach dziedziczenia pojedynczego, takich jak PHP. ... Jest dodatkiem do tradycyjnego dziedzictwa i umożliwia horyzontalne zestawienie zachowań; to znaczy zastosowanie członków klasy bez konieczności dziedziczenia.

Przykład:

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

Po zdefiniowaniu powyższej cechy mogę teraz wykonać następujące czynności:

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

W tym momencie, kiedy tworzę instancję klasy MyClass, ma ona dwie metody, wywoływane foo()i bar()- które pochodzą myTrait. I - zauważ, że traitmetody -defined mają już treść metody - czego Interfacemetoda -defined nie może.

Dodatkowo - PHP, podobnie jak wiele innych języków, korzysta z jednego modelu dziedziczenia - co oznacza, że ​​klasa może pochodzić z wielu interfejsów, ale nie z wielu klas. Jednak klasa PHP może mieć wiele traitinkluzji - co pozwala programiście uwzględniać elementy wielokrotnego użytku - podobnie jak w przypadku wielu klas podstawowych.

Kilka rzeczy do zapamiętania:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

Wielopostaciowość:

We wcześniejszym przykładzie, gdzie MyClass rozciąga się SomeBaseClass , MyClass jest instancja SomeBaseClass. Innymi słowy, tablica, która SomeBaseClass[] basesmoże zawierać instancje MyClass. Podobnie, jeśli MyClassrozszerzony IBaseInterface, tablica IBaseInterface[] basesmoże zawierać instancje MyClass. Nie ma takiej konstrukcji polimorficznej dostępnej z trait- ponieważ a traitjest zasadniczo tylko kodem, który jest kopiowany dla wygody programisty do każdej klasy, która go używa.

Precedens:

Jak opisano w instrukcji:

Dziedziczony element członkowski z klasy podstawowej jest zastępowany przez element wstawiony przez cechę. Pierwszeństwo jest takie, że członkowie z bieżącej klasy zastępują metody Trait, które w zamian zastępują metody dziedziczone.

Więc - rozważ następujący scenariusz:

class BaseClass {
    function SomeMethod() { /* Do stuff here */ }
}

interface IBase {
    function SomeMethod();
}

trait myTrait {
    function SomeMethod() { /* Do different stuff here */ }
}

class MyClass extends BaseClass implements IBase {
    use myTrait;

    function SomeMethod() { /* Do a third thing */ }
}

Podczas tworzenia wystąpienia MyClass powyżej występują następujące zdarzenia:

  1. Interface IBaseWymaga funkcji bez parametrów nazwie SomeMethod()mają zostać przekazane.
  2. Klasa podstawowa BaseClasszapewnia implementację tej metody - zaspokajając potrzebę.
  3. trait myTraitOferuje funkcję o nazwie bez parametrów SomeMethod(), jak również, który ma pierwszeństwo nad BaseClass-version
  4. class MyClassZapewnia własną wersję SomeMethod()- który ma pierwszeństwo nad trait-version.

Wniosek

  1. Puszka Interfacenie może zapewnić domyślnej implementacji treści metody, natomiast traitpuszka.
  2. An Interfacejest polimorficznym , odziedziczonym konstruktem - podczas gdy a traitnie.
  3. Wiele Interfaces może być używanych w tej samej klasie, podobnie jak wiele traits.
Troy Alford
źródło
4
„Cecha jest podobna do koncepcji C # klasy abstrakcyjnej” Nie, klasa abstrakcyjna jest klasą abstrakcyjną; ta koncepcja istnieje zarówno w PHP, jak i C #. Porównałbym cechę w PHP do klasy statycznej wykonanej z metod rozszerzenia w C #, z usuniętym ograniczeniem opartym na typach, ponieważ cecha może być używana przez prawie każdy typ, w przeciwieństwie do metody rozszerzenia, która rozszerza tylko jeden typ.
BoltClock
1
Bardzo dobry komentarz - i zgadzam się z tobą. Przy ponownym czytaniu jest to lepsza analogia. Uważam jednak, że lepiej jest myśleć o tym jak o tym mixin- i kiedy ponownie wróciłem do otwarcia mojej odpowiedzi, zaktualizowałem ją, aby to odzwierciedlić. Dzięki za komentowanie, @BoltClock!
Troy Alford
1
Nie sądzę, że istnieje jakikolwiek związek z metodami rozszerzenia c #. Metody rozszerzeń są dodawane do jednego typu klasy (oczywiście z poszanowaniem hierarchii klas), ich celem jest ulepszenie typu o dodatkowe funkcje, a nie „dzielenie się kodem” przez wiele klas i zrobienie bałaganu. To nie jest porównywalne! Jeśli coś trzeba ponownie wykorzystać, zwykle oznacza to, że powinno ono mieć własną przestrzeń, jak osobna klasa, która byłaby powiązana z klasami, które wymagają wspólnej funkcjonalności. Implementacja może się różnić w zależności od projektu, ale mniej więcej tyle. Cechy to po prostu kolejny sposób na zrobienie złego kodu.
Sofija
Klasa może mieć wiele interfejsów? Nie jestem pewien, czy źle popełniam twój wykres, ale klasa X implementuje Y, Z jest poprawne.
Yann Chabot,
26

Myślę traits przydatne są tworzenie klas zawierających metody, które można wykorzystać jako metody kilku różnych klas.

Na przykład:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

Możesz mieć i używać tej metody „błędu” w dowolnej klasie, która korzysta z tej cechy.

class Something
{
    use Toolkit;

    public function do_something($zipcode)
    {
        if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
            return $this->error('Invalid zipcode.');

        // do something here
    }
}

Podczas gdy interfacesmożesz deklarować tylko podpis metody, ale nie kod jej funkcji. Ponadto, aby użyć interfejsu, musisz przestrzegać hierarchii, używającimplements . Nie dotyczy to cech.

Jest zupełnie inaczej!

J. Bruni
źródło
Myślę, że to zły przykład cechy. to_integerprawdopodobnie byłby zawarty w IntegerCastinterfejsie, ponieważ nie ma zasadniczo podobnego sposobu (inteligentnego) rzutowania klas na liczbę całkowitą.
Matthew
5
Zapomnij o „to_integer” - to tylko ilustracja. Przykład. „Witaj, świecie”. „Example.com”.
J. Bruni,
2
Jakie korzyści zapewnia ta cecha zestawu narzędzi, której nie mogłaby zapewnić samodzielna klasa narzędzi? Zamiast use Toolkitciebie mógłbyś mieć, $this->toolkit = new Toolkit();czy też brakuje mi korzyści z samej cechy?
Anthony
@Anthony gdzieś w Somethingpojemniku robiszif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101 17.04.15
20

Dla początkujących powyżej odpowiedź może być trudna. Jest to najłatwiejszy sposób na jej zrozumienie:

Cechy

trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}

więc jeśli chcesz mieć sayHellofunkcję w innych klasach bez ponownego tworzenia całej funkcji, możesz użyć cech,

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

Fajnie, prawda!

Nie tylko funkcje można wykorzystać dowolną cechę (funkcja, zmienne, const ...). możesz także użyć wielu cech:use SayWorld,AnotherTraits;

Berło

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

tak więc interfejs różni się od cech: Musisz odtworzyć wszystko w interfejsie w zaimplementowanej klasie. interfejs nie ma implementacji. a interfejs może mieć tylko funkcje i stałą, nie może mieć zmiennych.

Mam nadzieję, że to pomoże!

Supun Praneeth
źródło
5

Często stosowaną metaforą opisującą cechy jest to, że cechy są interfejsami z implementacją.

Jest to dobry sposób myślenia o tym w większości przypadków, ale istnieje między nimi kilka subtelnych różnic.

Na początek instanceofoperator nie będzie pracował z cechami (tj. Cecha nie jest prawdziwym przedmiotem), więc nie możesz nam tego zrobić, aby sprawdzić, czy klasa ma określoną cechę (lub sprawdzić, czy dwie inne niezwiązane klasy mają wspólną cechę ). Rozumie się przez to, że jest to konstrukcja do ponownego wykorzystania kodu poziomego.

W PHP teraz funkcje, które pozwolą ci uzyskać listę wszystkich cech używanych przez klasę, ale dziedziczenie cech oznacza, że ​​będziesz musiał wykonywać rekurencyjne kontrole, aby niezawodnie sprawdzić, czy klasa w którymś momencie ma określoną cechę (istnieje przykład kod na stronach doco PHP). Ale tak, z pewnością nie jest tak prosty i czysty jak instanceof, a IMHO to funkcja, która poprawiłaby PHP.

Ponadto klasy abstrakcyjne są nadal klasami, więc nie rozwiązują problemów związanych z ponownym użyciem kodu związanych z wielokrotnym dziedziczeniem. Pamiętaj, że możesz rozszerzyć tylko jedną klasę (rzeczywistą lub abstrakcyjną), ale zaimplementować wiele interfejsów.

Przekonałem się, że cechy i interfejsy są naprawdę dobre do użycia razem w celu stworzenia pseudo wielokrotnego dziedziczenia. Na przykład:

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

Oznacza to, że możesz użyć instanceof, aby ustalić, czy dany obiekt Door jest Keyed, czy nie, wiesz, że otrzymasz spójny zestaw metod itp., A cały kod znajduje się w jednym miejscu we wszystkich klasach, które używają KeyedTrait.

Jon Kloske
źródło
Ostatnią częścią tej odpowiedzi jest oczywiście to, co @rdlowrey mówi bardziej szczegółowo w ostatnich trzech akapitach w części „Cechy” w swoim poście; Po prostu czułem, że bardzo prosty fragment kodu szkieletowego pomoże to zilustrować.
Jon Kloske
Myślę, że najlepszym sposobem na wykorzystanie cech OO jest użycie interfejsów tam, gdzie możesz. A jeśli zachodzi przypadek, gdy wiele podklas implementuje ten sam rodzaj kodu dla tego interfejsu i nie można przenieść tego kodu do ich (abstrakcyjnej) superklasy -> zaimplementować go z cechami
player-one
4

Cechy są po prostu do ponownego użycia kodu .

Interfejs po prostu zapewnia podpis funkcji, które mają być zdefiniowane w klasie, gdzie można go używać w zależności od uznania programisty . Daje nam to prototyp dla grupy klas .

W celach informacyjnych - http://www.php.net/manual/en/language.oop5.traits.php

Rajesh Paul
źródło
3

Zasadniczo można uznać tę cechę za zautomatyzowane „kopiowanie-wklejanie” kodu.

Używanie cech jest niebezpieczne, ponieważ nie ma sposobu, aby wiedzieć, co robi przed wykonaniem.

Jednak cechy są bardziej elastyczne ze względu na brak ograniczeń, takich jak dziedziczenie.

Cechy mogą być przydatne do wstrzyknięcia metody, która sprawdza coś w klasie, na przykład istnienie innej metody lub atrybutu. Niezły artykuł na ten temat (ale przepraszam po francusku) .

Dla osób czytających język francuski, które mogą je zdobyć, w GNU / Linux Magazine HS 54 znajduje się artykuł na ten temat.

Benj
źródło
Nadal nie rozumiem, czym różnią się cechy od interfejsów z domyślną implementacją
denis631,
@ denis631 Możesz zobaczyć Cechy jako fragmenty kodu, a interfejsy jako umowy podpisu. Jeśli może pomóc, możesz to postrzegać jako nieformalną klasę, która może zawierać wszystko. Daj mi znać, jeśli to pomoże.
Benj
Widzę, że cechy PHP mogą być postrzegane jako makra, które są następnie rozszerzane w czasie kompilacji / po prostu aliasing tego fragmentu kodu za pomocą tego klucza. Jednak cechy rdzy wyglądają inaczej (lub się mylę). Ale ponieważ oboje mają w sobie cechę słowną, zakładam, że są takie same, co oznacza tę samą koncepcję. Link do cech rdzy: doc.rust-lang.org/rust-by-example/trait.html
denis631
2

Jeśli znasz angielski i wiesz, co to traitznaczy, dokładnie tak mówi nazwa. Jest to bezklasowy zestaw metod i właściwości, które dołączasz do istniejących klas, wpisując use.

Zasadniczo można go porównać do pojedynczej zmiennej. Funkcje zamknięcia mogą usete zmienne spoza zakresu i w ten sposób mają wartość wewnątrz. Są potężne i można je stosować we wszystkim. To samo dzieje się z cechami, jeśli są one używane.

Thielicious
źródło
2

Inne odpowiedzi świetnie spisały się wyjaśniając różnice między interfejsami i cechami. Skoncentruję się na przydatnym przykładzie ze świata rzeczywistego, w szczególności na tym, który pokazuje, że cechy mogą wykorzystywać zmienne instancji - pozwalając na dodanie zachowania do klasy przy minimalnym kodzie wzorcowym.

Ponownie, jak wspomniano przez innych, cechy dobrze łączą się z interfejsami, umożliwiając interfejsowi określenie kontraktu behawioralnego i cechę spełniającą implementację.

Dodanie możliwości publikowania / subskrybowania zdarzeń do klasy może być częstym scenariuszem w niektórych bazach kodu. Istnieją 3 popularne rozwiązania:

  1. Zdefiniuj klasę podstawową za pomocą kodu publikacji / kodu zdarzenia, a następnie klasy, które chcą oferować zdarzenia, mogą ją rozszerzyć w celu uzyskania możliwości.
  2. Zdefiniuj klasę z kodem zdarzenia pub / subcode, a następnie inne klasy, które chcą oferować zdarzenia, mogą korzystać z niej poprzez kompozycję, definiując własne metody zawijania złożonego obiektu, przybliżając wywołania metod do niego.
  3. Zdefiniuj cechę za pomocą publikacji / subkodu zdarzeń, a następnie inne klasy, które chcą oferować wydarzenia, mogą usetę cechę, czyli ją zaimportować, w celu uzyskania możliwości.

Jak dobrze działa każdy?

# 1 Nie działa dobrze. Tak będzie, dopóki nie zdasz sobie sprawy, że nie możesz rozszerzyć klasy podstawowej, ponieważ już rozszerzasz coś innego. Nie będę tego pokazywał, ponieważ powinno być oczywiste, jak ograniczające jest używanie takiego dziedziczenia.

# 2 i # 3 oba działają dobrze. Pokażę przykład, który podkreśla pewne różnice.

Po pierwsze, kod, który będzie taki sam między oboma przykładami:

Interfejs

interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}

I trochę kodu do zademonstrowania użycia:

$auction = new Auction();

// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
    echo "Got a bid of $bidAmount from $bidderName\n";
});

// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
    $auction->addBid($name, rand());
}

Ok, teraz pokażmy, jak Auctionróżni się implementacja klasy przy użyciu cech.

Po pierwsze, oto jak wyglądałby numer 2 (przy użyciu kompozycji):

class EventEmitter {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    private $eventEmitter;

    public function __construct() {
        $this->eventEmitter = new EventEmitter();
    }

    function addBid($bidderName, $bidAmount) {
        $this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
    }

    function addEventListener($eventName, callable $listener) {
        $this->eventEmitter->addEventListener($eventName, $listener);
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventEmitter->removeEventListener($eventName, $listener);
    }

    function removeAllEventListeners($eventName) {
        $this->eventEmitter->removeAllEventListeners($eventName);
    }
}

Oto jak wyglądałoby # 3 (cechy):

trait EventEmitterTrait {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    protected function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    use EventEmitterTrait;

    function addBid($bidderName, $bidAmount) {
        $this->triggerEvent('bid', [$bidderName, $bidAmount]);
    }
}

Zauważ, że kod wewnątrz EventEmitterTraitjest dokładnie taki sam, jak wewnątrz EventEmitterklasy, z wyjątkiem tego, że cecha deklaruje triggerEvent()metodę jako chronioną. Tak więc jedyną różnicą, na którą musisz spojrzeć, jest implementacja Auctionklasy .

Różnica jest duża. Korzystając z kompozycji, otrzymujemy świetne rozwiązanie, które pozwala nam ponownie wykorzystać naszą EventEmitterliczbę zajęć. Ale główną wadą jest to, że mamy dużo kodu typu „płyta podstawowa”, który musimy napisać i zachować, ponieważ dla każdej metody zdefiniowanej w Observableinterfejsie musimy go zaimplementować i napisać nudny kod typu „płyta podstawowa”, który po prostu przekazuje argumenty na odpowiednią metodę w nasz komponował EventEmitterobiekt. Użycie cechy z tego przykładu pozwala nam tego uniknąć , pomagając nam zredukować ogólny kod i poprawić łatwość konserwacji .

Może się jednak zdarzyć, że nie będziesz chciał, aby twoja Auctionklasa zaimplementowała pełny Observableinterfejs - być może chcesz tylko ujawnić 1 lub 2 metody, a może nawet żadną, abyś mógł zdefiniować własne podpisy metod. W takim przypadku nadal możesz preferować metodę kompozycji.

Ale ta cecha jest bardzo atrakcyjna w większości scenariuszy, szczególnie jeśli interfejs ma wiele metod, co powoduje, że piszesz dużo schematu.

* Możesz właściwie zrobić jedno i drugie - zdefiniować EventEmitterklasę na wypadek, gdybyś chciał użyć jej kompozycyjnie, a także zdefiniować EventEmitterTraitcechę, używając EventEmitterimplementacji klasy wewnątrz cechy :)

Koza
źródło
1

Ta cecha jest taka sama jak klasa, której możemy używać do celów wielokrotnego dziedziczenia, a także do ponownego użycia kodu.

Możemy użyć cechy w klasie, a także możemy użyć wielu cech w tej samej klasie za pomocą słowa „użyj słowa kluczowego”.

Interfejs używa tego samego kodu do wielokrotnego użytku kodu

interfejs rozszerza wiele interfejsów, dzięki czemu możemy rozwiązać wiele problemów związanych z dziedziczeniem, ale kiedy implementujemy interfejs, powinniśmy stworzyć wszystkie metody wewnątrz klasy. Aby uzyskać więcej informacji, kliknij poniższy link:

http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php

Chirag Prajapati
źródło
1

Interfejs to umowa, która mówi, że „ten obiekt jest w stanie to zrobić”, podczas gdy cecha daje temu obiektowi możliwość robienia tego.

Cecha jest zasadniczo sposobem „kopiowania i wklejania” kodu między klasami.

Spróbuj przeczytać ten artykuł. Jakie są cechy PHP?

Hos Mercury
źródło
0

Główna różnica polega na tym, że w przypadku interfejsów należy zdefiniować faktyczną implementację każdej metody w ramach każdej klasy, która implementuje wspomniany interfejs, dzięki czemu można mieć wiele klas implementujących ten sam interfejs, ale o różnych zachowaniach, podczas gdy cechy to tylko fragmenty kodu Klasa; Inną ważną różnicą jest to, że metody cech mogą być jedynie metodami klasowymi lub statycznymi, w przeciwieństwie do metod interfejsowych, które mogą (i zwykle są) metodami instancji.

Alessandro Martin
źródło