Tratti vs. interfacce


346

Ultimamente ho cercato di studiare PHP e mi ritrovo a rimanere impiccato sui tratti. Comprendo il concetto di riutilizzo del codice orizzontale e non voglio necessariamente ereditare da una classe astratta. Quello che non capisco è: qual è la differenza cruciale tra l'uso dei tratti rispetto alle interfacce?

Ho provato a cercare un post o un articolo di blog decente che spiegasse quando usare l'uno o l'altro, ma gli esempi che ho trovato finora sembrano così simili da essere identici.


6
l'interfaccia non ha alcun codice nei corpi funzione. in realtà non hanno corpi funzionali.
hakre,

2
Nonostante la mia risposta molto votata, mi piacerebbe dire per la cronaca che sono generalmente anti-tratto / mixin . Controlla questa trascrizione della chat per leggere come i tratti spesso minano le solide pratiche OOP .
rdlowrey,

2
Direi il contrario. Avendo lavorato con PHP per anni prima e dall'avvento dei tratti, penso che sia facile dimostrare il loro valore. Basta leggere questo esempio pratico che consente ai "modelli di immagini" di camminare e parlare anche come Imagickoggetti, meno tutto il gonfiore necessario ai vecchi tempi prima dei tratti.
cambio rapido il

I tratti e l'interfaccia sono simili. La differenza principale è che i tratti ti consentono di implementare i metodi, l'interfaccia no.
Giovanni

Risposte:


239

Un'interfaccia definisce una serie di metodi che la classe di implementazione deve implementare.

Quando un tratto è use, anche le implementazioni dei metodi arrivano - cosa che non accade in un Interface.

Questa è la differenza più grande.

Dal riutilizzo orizzontale per PHP RFC :

I tratti sono un meccanismo per il riutilizzo del codice in singoli linguaggi di eredità come PHP. Un tratto ha lo scopo di ridurre alcune limitazioni della singola eredità consentendo a uno sviluppatore di riutilizzare liberamente gruppi di metodi in diverse classi indipendenti che vivono in gerarchie di classi diverse.


2
@JREAM In pratica, niente. In realtà, molto di più.
Alec Gorge,

79
Tranne che i tratti non sono affatto interfacce. Le interfacce sono specifiche che possono essere verificate. I tratti non possono essere verificati, quindi sono solo implementazione. Sono l'esatto contrario delle interfacce. Quella linea nella RFC è semplicemente sbagliata ...
Ircmaxell,

196
I tratti sono essenzialmente copia e incolla assistita dalla lingua .
Shahid,

10
Questa non è una metafora. Questo sta macellando il significato di una parola. È come descrivere una scatola come una superficie con volume.
Cleong,

6
Per espandere i commenti di ircmaxell e Shadi: Puoi verificare se un oggetto implementa un'interfaccia (tramite istanza di) e puoi assicurarti che un argomento del metodo implementi un'interfaccia tramite un suggerimento di tipo nella firma del metodo. Non è possibile eseguire controlli corrispondenti per i tratti.
Brian D'Astous,

531

Annuncio di servizio pubblico:

Voglio dire per la cronaca che credo che i tratti siano quasi sempre un odore di codice e che dovrebbero essere evitati a favore della composizione. Ritengo che l'eredità singola sia spesso abusata al punto da essere un modello anti-modello e l'ereditarietà multipla aggrava solo questo problema. Sarai servito molto meglio nella maggior parte dei casi favorendo la composizione rispetto all'eredità (sia essa singola o multipla). Se sei ancora interessato ai tratti e alla loro relazione con le interfacce, continua a leggere ...


Cominciamo dicendo questo:

La programmazione orientata agli oggetti (OOP) può essere un paradigma difficile da comprendere. Solo perché stai usando le classi non significa che il tuo codice è orientato agli oggetti (OO).

Per scrivere il codice OO devi capire che OOP riguarda davvero le capacità dei tuoi oggetti. Devi pensare alle lezioni in termini di cosa possono fare invece di quello che fanno realmente . Ciò è in netto contrasto con la programmazione procedurale tradizionale in cui l'attenzione è focalizzata sul fare un po 'di codice "fare qualcosa".

Se il codice OOP riguarda la pianificazione e la progettazione, un'interfaccia è il modello e un oggetto è la casa completamente costruita. Nel frattempo, i tratti sono semplicemente un modo per aiutare a costruire la casa definita dal progetto (l'interfaccia).

interfacce

Quindi, perché dovremmo usare le interfacce? Molto semplicemente, le interfacce rendono il nostro codice meno fragile. Se dubiti di questa affermazione, chiedi a chiunque sia stato costretto a mantenere un codice legacy che non è stato scritto contro le interfacce.

L'interfaccia è un contratto tra il programmatore e il suo codice. L'interfaccia dice: "Finché giochi secondo le mie regole, puoi implementarmi come preferisci e prometto che non infrangerò il tuo altro codice."

Quindi, ad esempio, considera uno scenario del mondo reale (senza auto o widget):

Si desidera implementare un sistema di memorizzazione nella cache per un'applicazione Web per ridurre il carico del server

Si inizia scrivendo una classe per memorizzare nella cache le risposte alle richieste utilizzando 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);
  }
}

Quindi, nell'oggetto risposta HTTP, si verifica un hit nella cache prima di fare tutto il lavoro per generare la risposta effettiva:

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

Questo approccio funziona alla grande. Ma forse qualche settimana dopo decidi di voler utilizzare un sistema di cache basato su file anziché APC. Ora devi modificare il codice del controller perché hai programmato il controller in modo che funzioni con la funzionalità della ApcCacherclasse piuttosto che con un'interfaccia che esprima le capacità della ApcCacherclasse. Diciamo che invece di quanto sopra hai fatto Controlleraffidare la classe su un CacherInterfaceinvece del concreto in questo ApcCachermodo:

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

Per far ciò, definisci la tua interfaccia in questo modo:

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

A sua volta, sia tu ApcCacherche le tue nuove FileCacherclassi implementiamo CacherInterfacee programmate la vostra Controllerclasse per utilizzare le funzionalità richieste dall'interfaccia.

Questo esempio (si spera) dimostra come la programmazione su un'interfaccia ti permetta di cambiare l'implementazione interna delle tue classi senza preoccuparti se le modifiche interromperanno il tuo altro codice.

Tratti

I tratti, d'altra parte, sono semplicemente un metodo per riutilizzare il codice. Le interfacce non dovrebbero essere pensate come un'alternativa reciprocamente esclusiva ai tratti. In effetti, la creazione di tratti che soddisfano le capacità richieste da un'interfaccia è il caso d'uso ideale .

Dovresti usare i tratti solo quando più classi condividono la stessa funzionalità (probabilmente dettata dalla stessa interfaccia). Non ha senso utilizzare un tratto per fornire funzionalità per una singola classe: ciò offusca solo ciò che fa la classe e un design migliore sposterebbe la funzionalità del tratto nella classe pertinente.

Considera la seguente implementazione del tratto:

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!';
    }
}

Un esempio più concreto: immagina che tu FileCachere il tuo ApcCacherdalla discussione dell'interfaccia utilizzino lo stesso metodo per determinare se una voce della cache è obsoleta e deve essere eliminata (ovviamente questo non è il caso nella vita reale, ma seguilo). È possibile scrivere un tratto e consentire a entrambe le classi di utilizzarlo per i requisiti dell'interfaccia comune.

Un'ultima parola di cautela: fai attenzione a non esagerare con i tratti. Spesso i tratti sono usati come stampelle per un design scadente quando sarebbero sufficienti implementazioni di classe uniche. Dovresti limitare i tratti al soddisfacimento dei requisiti di interfaccia per la migliore progettazione del codice.


69
Stavo davvero cercando la risposta semplice e rapida fornita sopra, ma devo dire che hai dato un'eccellente risposta approfondita che aiuterà a rendere più chiara la distinzione per gli altri, complimenti.
datguywhowers

35
"[C] tratti distintivi che soddisfano le capacità richieste da un'interfaccia in una data classe è un caso d'uso ideale". Esatto: +1
Alec Gorge,

5
Sarebbe giusto dire che i tratti in PHP sono simili ai mixin in altre lingue?
Eno,

5
@igorpan A tutti gli effetti direi che l'implementazione del tratto di PHP è la stessa dell'ereditarietà multipla. Vale la pena notare che se un tratto in PHP specifica proprietà statiche, ogni classe che usa il tratto avrà la propria copia della proprietà statica. Ancora più importante ... vedendo come questo post è ora estremamente in alto nelle SERP quando interrogo per i tratti aggiungerò un annuncio di servizio pubblico nella parte superiore della pagina. Dovresti leggerlo.
rdlowrey,

3
+1 per una spiegazione approfondita. Vengo da uno sfondo rubino, dove i mixin sono usati MOLTO; solo per aggiungere i miei due centesimi, una buona regola empirica che usiamo potrebbe essere tradotta in php in quanto "non implementare metodi che mutano $ this nei tratti". Questo impedisce un sacco di pazze sessioni di debug ... Un mixin NON dovrebbe inoltre fare ipotesi sulla classe in cui verrà mescolato (o dovresti renderlo molto chiaro e ridurre le dipendenze al minimo indispensabile). A questo proposito, trovo piacevole la tua idea di tratti che implementano interfacce.
m_x,

67

A traitè essenzialmente l'implementazione di PHP di a mixin, ed è effettivamente un insieme di metodi di estensione che possono essere aggiunti a qualsiasi classe mediante l'aggiunta di trait. I metodi diventano quindi parte dell'implementazione di quella classe, ma senza usare l'ereditarietà .

Dal manuale di PHP (enfasi sulla mia):

I tratti sono un meccanismo per il riutilizzo del codice in singoli linguaggi di ereditarietà come PHP. ... è un'aggiunta all'eredità tradizionale e consente la composizione orizzontale del comportamento; cioè, l'applicazione dei membri della classe senza richiedere l'ereditarietà.

Un esempio:

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

Con il tratto sopra definito, ora posso fare quanto segue:

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

A questo punto, quando creo un'istanza di classe MyClass, ha due metodi, chiamato foo()e bar()- da cui provengono myTrait. E - nota che i traitmetodi definiti hanno già un corpo del metodo - che un Interfacemetodo definito non può.

Inoltre, PHP, come molti altri linguaggi, utilizza un singolo modello di ereditarietà, il che significa che una classe può derivare da più interfacce, ma non da più classi. Tuttavia, una classe PHP può avere traitinclusioni multiple - che consente al programmatore di includere pezzi riutilizzabili - come potrebbero includere se includessero più classi base.

Alcune cose da notare:

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

Polimorfismo:

Nell'esempio precedente, dove si MyClass estende SomeBaseClass , MyClass è un'istanza di SomeBaseClass. In altre parole, un array come SomeBaseClass[] basespuò contenere istanze di MyClass. Allo stesso modo, se MyClassesteso IBaseInterface, un array di IBaseInterface[] basespotrebbe contenere istanze di MyClass. Non esiste un tale costrutto polimorfico disponibile con a trait- perché a traitè essenzialmente un codice che viene copiato per comodità del programmatore in ogni classe che lo utilizza.

Precedenza:

Come descritto nel manuale:

Un membro ereditato da una classe base viene sovrascritto da un membro inserito da un tratto. L'ordine di precedenza è che i membri della classe corrente sovrascrivono i metodi Trait, che a loro volta sostituiscono i metodi ereditati.

Quindi, considera il seguente scenario:

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 */ }
}

Quando si crea un'istanza di MyClass, sopra, si verifica quanto segue:

  1. Il Interface IBaserichiede una funzione senza parametri chiamato SomeMethod()da fornire.
  2. La classe base BaseClassfornisce un'implementazione di questo metodo - soddisfacendo la necessità.
  3. La trait myTraitfornisce una funzione senza parametri chiamato SomeMethod()pure, che prevale sul BaseClass-version
  4. Il class MyClassfornisce la propria versione di SomeMethod()- che prevale sul trait-version.

Conclusione

  1. Un Interfacenon può fornire un'implementazione predefinita di un corpo del metodo, mentre un traitcan.
  2. An Interfaceè un costrutto polimorfico ereditato , mentre a traitnon lo è.
  3. Più Interfaces possono essere usati nella stessa classe, così come più traits.

4
"Un tratto è simile al concetto C # di una classe astratta" No, una classe astratta è una classe astratta; quel concetto esiste sia in PHP che in C #. Confronterei un tratto in PHP con una classe statica fatta di metodi di estensione in C #, con la restrizione basata sul tipo rimossa poiché un tratto può essere usato praticamente da qualsiasi tipo, a differenza di un metodo di estensione che estende solo un tipo.
BoltClock

1
Ottimo commento - e sono d'accordo con te. Nel rileggere, questa è un'analogia migliore. Credo che sia comunque meglio pensarlo come un mixin- e mentre ho rivisitato l'apertura della mia risposta, ho aggiornato per riflettere questo. Grazie per aver commentato, @BoltClock!
Troy Alford,

1
Non penso che ci sia alcuna relazione con i metodi di estensione c #. I metodi di estensione vengono aggiunti al tipo di singola classe (rispettando ovviamente la gerarchia di classi) il loro scopo è quello di migliorare un tipo con funzionalità aggiuntive, non di "condividere il codice" su più classi e creare un pasticcio. Non è paragonabile! Se qualcosa deve essere riutilizzato, di solito significa che dovrebbe avere spazio proprio, come una classe separata che sarebbe correlata alle classi che necessitano di funzionalità comuni. L'implementazione può variare a seconda del design, ma più o meno è quello. I tratti sono solo un altro modo per creare un codice scadente.
Sofija,

Una classe può avere più interfacce? Non sono sicuro che stia sbagliando il tuo grafico, ma la classe X implementa Y, Z è valida.
Yann Chabot,

26

Penso che traitssiano utili per creare classi che contengono metodi che possono essere usati come metodi di diverse classi.

Per esempio:

trait ToolKit
{
    public $errors = array();

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

Puoi avere e utilizzare questo metodo di "errore" in qualsiasi classe che utilizza questo tratto.

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

Mentre con interfaceste puoi solo dichiarare la firma del metodo, ma non il codice delle sue funzioni. Inoltre, per utilizzare un'interfaccia è necessario seguire una gerarchia, utilizzando implements. Questo non è il caso dei tratti.

È completamente diverso!


Penso che questo sia un cattivo esempio di tratto. to_integersarebbe più probabilmente incluso in IntegerCastun'interfaccia perché non esiste un modo fondamentalmente simile alle classi (in modo intelligente) di cast a un numero intero.
Matteo,

5
Dimentica "to_integer": è solo un'illustrazione. Un esempio. Un "Ciao, mondo". Un "esempio.com".
J. Bruni,

2
Quali vantaggi offre questo tratto di toolkit che una classe di utilità standalone non potrebbe? Invece di use Toolkitte potresti avere $this->toolkit = new Toolkit();o mi sto perdendo qualche beneficio del tratto stesso?
Anthony,

@Anthony da qualche parte nel Somethingcontenitore che faiif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101

20

Per i principianti sopra la risposta potrebbe essere difficile, questo è il modo più semplice per capirlo:

Tratti

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

quindi se vuoi avere una sayHellofunzione in altre classi senza ricreare l'intera funzione puoi usare tratti,

class MyClass{
  use SayWorld;

}

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

Bene!

Non solo funzioni puoi usare qualsiasi cosa nel tratto (funzione, variabili, const ..). inoltre puoi usare più tratti:use SayWorld,AnotherTraits;

Interfaccia

  interface SayWorld {
     public function sayHello();
  }

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

quindi ecco come l'interfaccia differisce dai tratti: devi ricreare tutto nell'interfaccia nella classe implementata. l'interfaccia non ha implementazione. e l'interfaccia può avere solo funzioni e const, non può avere variabili.

Spero che aiuti!


5

Una metafora spesso usata per descrivere i tratti è che i tratti sono interfacce con l'implementazione.

Questo è un buon modo di pensarci nella maggior parte dei casi, ma ci sono una serie di sottili differenze tra i due.

Per cominciare, il instanceof operatore non lavorerà con i tratti (cioè un tratto non è un oggetto reale) quindi non puoi farci vedere se una classe ha un certo tratto (o vedere se due classi altrimenti non correlate condividono un tratto ). Questo è ciò che intendono per essere un costrutto per il riutilizzo del codice orizzontale.

Ci sono funzioni ora in PHP che ti permetteranno di ottenere un elenco di tutti i tratti utilizzati da una classe, ma l'ereditarietà dei tratti significa che dovrai fare controlli ricorsivi per verificare in modo affidabile se una classe ad un certo punto ha un tratto specifico (c'è un esempio codice nelle pagine dei documenti PHP). Ma sì, non è certamente così semplice e pulito come esempio diof, e IMHO è una caratteristica che renderebbe PHP migliore.

Inoltre, le classi astratte sono ancora classi, quindi non risolvono problemi di riutilizzo del codice relativi all'ereditarietà multipla. Ricorda che puoi estendere solo una classe (reale o astratta) ma implementare più interfacce.

Ho scoperto che tratti e interfacce sono davvero buoni da usare mano nella mano per creare eredità pseudo multiple. Per esempio:

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

Ciò significa che puoi usare instanceof per determinare se il particolare oggetto Door è Keyed oppure no, sai che otterrai un set coerente di metodi ecc. E tutto il codice è in un posto in tutte le classi che usano KeyedTrait.


L'ultima parte di quella risposta è ovviamente ciò che @rdlowrey sta dicendo più in dettaglio negli ultimi tre paragrafi sotto "Tratti" nel suo post; Ho solo pensato che un frammento di codice scheletro davvero semplice mi avrebbe aiutato a illustrarlo.
Jon Kloske,

Penso che il modo migliore OO per usare i tratti sia usare le interfacce dove puoi. E se c'è un caso in cui più sottoclassi implementano lo stesso tipo di codice per quell'interfaccia e non puoi spostare quel codice nella loro (astratta) superclasse -> implementalo con tratti
player-one

4

I tratti sono semplicemente per il riutilizzo del codice .

Interface fornisce solo la firma delle funzioni che devono essere definite nella classe in cui può essere utilizzata a seconda della discrezione del programmatore . Dandoci così un prototipo per un gruppo di classi .

Per riferimento: http://www.php.net/manual/en/language.oop5.traits.php


3

In sostanza, puoi considerare un tratto come una "copia-incolla" automatica di codice.

L'uso dei tratti è pericoloso poiché non c'è modo di sapere cosa fa prima dell'esecuzione.

Tuttavia, i tratti sono più flessibili a causa della loro mancanza di limiti come l'ereditarietà.

I tratti possono essere utili per iniettare un metodo che controlla qualcosa in una classe, ad esempio l'esistenza di un altro metodo o attributo. Un bell'articolo su questo (ma in francese, scusa) .

Per le persone di lettura francese che possono ottenerlo, la rivista GNU / Linux HS 54 ha un articolo su questo argomento.


Ancora non capisco come i tratti siano diversi dalle interfacce con l'implementazione predefinita
denis631

@ denis631 Puoi vedere i tratti come frammenti di codice e le interfacce come contratti di firma. Se può aiutare, puoi vederlo come un bit informale di una classe che può contenere qualsiasi cosa. Fatemi sapere se aiuta.
Benj,

Vedo che i tratti di PHP possono essere visti come macro che vengono poi espanse in fase di compilazione / semplicemente aliasing quello snippet di codice con quella chiave. I tratti di ruggine, tuttavia, appaiono diversi (o sbaglio). Ma dal momento che entrambi hanno un tratto caratteristico, suppongo che siano uguali, intendendo lo stesso concetto. Link tratti della ruggine: doc.rust-lang.org/rust-by-example/trait.html
denis631

2

Se conosci l'inglese e sai cosa traitsignifica, è esattamente quello che dice il nome. Si tratta di un pacchetto senza classi di metodi e proprietà che si collegano alle classi esistenti digitando use.

Fondamentalmente, potresti confrontarlo con una singola variabile. Le funzioni di chiusura possono usequeste variabili dall'esterno dell'ambito e in questo modo hanno il valore all'interno. Sono potenti e possono essere utilizzati in tutto. Lo stesso succede ai tratti se vengono usati.


2

Altre risposte hanno fatto un ottimo lavoro nel spiegare le differenze tra interfacce e tratti. Mi concentrerò su un utile esempio del mondo reale, in particolare uno che dimostra che i tratti possono usare le variabili di istanza, permettendoti di aggiungere un comportamento a una classe con un minimo codice di boilerplate.

Ancora una volta, come menzionato da altri, i tratti si accoppiano bene con le interfacce, consentendo all'interfaccia di specificare il contratto di comportamento e il tratto per realizzare l'implementazione.

L'aggiunta di funzionalità di pubblicazione / sottoscrizione di eventi a una classe può essere uno scenario comune in alcune basi di codice. Esistono 3 soluzioni comuni:

  1. Definire una classe di base con pub / sottocodice di eventi, quindi le classi che desiderano offrire eventi possono estenderla per ottenere le funzionalità.
  2. Definire una classe con pub / sottocodice di eventi, quindi altre classi che vogliono offrire eventi possono usarlo tramite composizione, definendo i propri metodi per avvolgere l'oggetto composto, inoltrando il metodo che chiama.
  3. Definisci un tratto con pub / sottocodice di eventi, quindi altre classi che vogliono offrire eventi possono usetrarre il tratto, ovvero importarlo, per ottenere le funzionalità.

Quanto bene funziona ciascuno?

# 1 Non funziona bene. Sarebbe, fino al giorno in cui ti rendi conto che non puoi estendere la classe base perché stai già estendendo qualcos'altro. Non mostrerò un esempio di questo perché dovrebbe essere ovvio quanto sia limitativo usare l'eredità in questo modo.

# 2 e # 3 funzionano entrambi bene. Mostrerò un esempio che evidenzia alcune differenze.

Innanzitutto, un po 'di codice che sarà lo stesso tra entrambi gli esempi:

Un'interfaccia

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

E un po 'di codice per dimostrare l'utilizzo:

$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, ora mostriamo come l'implementazione della Auctionclasse differirà quando si usano i tratti.

Innanzitutto, ecco come sarebbe il n. 2 (usando la composizione):

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

Ecco come sarebbe il n. 3 (tratti):

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]);
    }
}

Si noti che il codice all'interno di EventEmitterTraitè esattamente lo stesso di ciò che è all'interno della EventEmitterclasse tranne il tratto dichiara il triggerEvent()metodo come protetto. Quindi, l'unica differenza che devi guardare è l'implementazione della Auctionclasse .

E la differenza è grande. Quando usiamo la composizione, otteniamo un'ottima soluzione, permettendoci di riutilizzare le nostre EventEmitterclassi quante ne vogliamo. Ma il principale svantaggio è che abbiamo un sacco di codice boilerplate che dobbiamo scrivere e mantenere perché per ogni metodo definito Observablenell'interfaccia, dobbiamo implementarlo e scrivere noioso codice boilerplate che inoltra gli argomenti sul metodo corrispondente in abbiamo composto l' EventEmitteroggetto. L'uso del tratto in questo esempio ci consente di evitarlo , aiutandoci a ridurre il codice della caldaia e migliorare la manutenibilità .

Tuttavia, ci possono essere momenti in cui non vuoi che la tua Auctionclasse implementi l' Observableinterfaccia completa - forse vuoi solo esporre 1 o 2 metodi, o forse addirittura nessuno, in modo da poter definire le tue firme dei metodi. In tal caso, potresti comunque preferire il metodo di composizione.

Ma il tratto è molto interessante nella maggior parte degli scenari, specialmente se l'interfaccia ha molti metodi, il che ti fa scrivere un sacco di piatti.

* In realtà potresti fare entrambe le cose: definire la EventEmitterclasse nel caso in cui tu voglia usarla in modo compositivo e definire anche il EventEmitterTraittratto, usando l' EventEmitterimplementazione della classe all'interno del tratto :)


1

Il tratto è lo stesso di una classe che possiamo usare per molteplici scopi di ereditarietà e anche riusabilità del codice.

Possiamo usare tratti all'interno della classe e anche usare tratti multipli nella stessa classe con 'usa parola chiave'.

L'interfaccia utilizza per la riusabilità del codice lo stesso di un tratto

l'interfaccia è estesa a più interfacce in modo da poter risolvere i molteplici problemi di ereditarietà ma quando implementiamo l'interfaccia dovremmo creare tutti i metodi all'interno della classe. Per maggiori informazioni clicca sotto il link:

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



0

La differenza principale è che, con le interfacce, è necessario definire l'implementazione effettiva di ciascun metodo all'interno di ogni classe che implementa detta interfaccia, quindi è possibile avere molte classi implementare la stessa interfaccia ma con comportamenti diversi, mentre i tratti sono solo blocchi di codice iniettati in una classe; un'altra differenza importante è che i metodi tratti possono essere solo metodi di classe o metodi statici, a differenza dei metodi di interfaccia che possono anche (e di solito sono) essere metodi di istanza.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.