Quando usare i tratti, al contrario dell'eredità e della composizione?


9

Esistono tre modi comuni, AFAIK, per implementare la riusabilità quando si tratta di OOP

  1. Eredità: di solito per rappresentare una relazione (un'anatra è un uccello)
  2. Composizione: di solito per rappresentare una relazione (una macchina ha un motore)
  3. Tratti (ad esempio la parola chiave tratto in PHP): ... non ne sono proprio sicuro

Mentre mi sembra che i tratti possano implementare sia le relazioni has-a che is-a, non sono sicuro del tipo di modellizzazione a cui era destinato. Per quale tipo di situazioni sono stati progettati i tratti?


Puoi spiegare qualcosa in più sui tratti, ma i tratti potrebbero essere solo un'altra parola per le composizioni?
Trilarion


1
Mi piacerebbe davvero sapere anche il perché. Non li ho usati una volta.
Andy,

Risposte:


6

I tratti sono un altro modo di fare composizione. Pensa a loro come un modo per comporre tutte le parti di una classe in fase di compilazione (o in fase di compilazione JIT), assemblando le implementazioni concrete delle parti necessarie.

Fondamentalmente, vuoi usare i tratti quando ti ritrovi a fare lezioni con diverse combinazioni di caratteristiche. Questa situazione si presenta più spesso per le persone che scrivono librerie flessibili che gli altri possono consumare. Ad esempio, ecco la dichiarazione di una classe di test unit che ho scritto di recente usando ScalaTest :

class TestMyClass
  extends WordSpecLike
  with Matchers
  with MyCustomTrait
  with BeforeAndAfterAll
  with BeforeAndAfterEach
  with ScalaFutures

I framework di unit test hanno moltissime opzioni di configurazione diverse e ogni team ha preferenze diverse su come vogliono fare le cose. Mettendo le opzioni in tratti (che si mescolano usando within Scala), ScalaTest può offrire tutte quelle opzioni senza dover creare nomi di classe simili WordSpecLikeWithMatchersAndFutures, o una tonnellata di flag booleani di runtime come WordSpecLike(enableFutures, enableMatchers, ...). Ciò semplifica il rispetto del principio di apertura / chiusura . Puoi aggiungere nuove funzionalità e nuove combinazioni di funzionalità semplicemente aggiungendo un nuovo tratto. Inoltre, semplifica il rispetto del principio di segregazione dell'interfaccia , poiché puoi facilmente inserire funzioni non universalmente necessarie in un tratto.

I tratti sono anche un buon modo per inserire codice comune in diverse classi che non hanno senso condividere una gerarchia di ereditarietà. L'ereditarietà è una relazione molto unita e non dovresti pagare quel costo se puoi aiutarlo. I tratti sono una relazione molto più debole. Nel mio esempio precedente, MyCustomTraitcondividevo facilmente un'implementazione del database finta tra diverse classi di test altrimenti non correlate.

L'iniezione di dipendenza raggiunge molti degli stessi obiettivi, ma in fase di esecuzione in base all'input dell'utente anziché al momento della compilazione in base all'input del programmatore. I tratti sono anche destinati più alle dipendenze che fanno parte semanticamente della stessa classe. Stai un po 'assemblando le parti di una classe piuttosto che effettuare chiamate verso altre classi con altre responsabilità.

I framework di iniezione delle dipendenze raggiungono molti degli stessi obiettivi in ​​fase di compilazione in base all'input del programmatore, ma sono in gran parte una soluzione alternativa per i linguaggi di programmazione senza un adeguato supporto del tratto. I tratti portano queste dipendenze nel regno del controllo del tipo del compilatore, con una sintassi più pulita, con un processo di compilazione più semplice, che fa una distinzione più chiara tra dipendenze in fase di compilazione e di runtime.


Ciao, questa è un'ottima risposta. Posso chiederti come decidere quando usare i tratti e quando usare l'iniezione di dipendenza. che sembra raggiungere lo stesso.
Extrakun,

Osservazione acuta. Vedi la mia modifica.
Karl Bielefeldt,

Grazie per averlo scritto. Sono curioso di sapere se la tua risposta è che i tratti sono come la composizione @KarlBielefeldt. La tua classe può essere usata ovunque sia necessario quel tratto, e tu soffriresti ancora della stessa fragilità di una normale interfaccia. Quindi sembra più vicino al sottotipo e all'eredità, no? Inoltre, non sono sicuro di come sia simile a DI ... se hai definito varie classi che estendono i tratti, allora non sono quelle dipendenze concrete definite ovunque nella gerarchia quella classe sia definita, piuttosto che in un punto superiore / di entrata del codice? Grazie!
allstar

I tratti possono essere usati proprio come le interfacce, per le loro proprietà ereditarie, ma non è ciò che li rende unici. L' withoperatore è ciò che li differenzia, ed withè un'operazione di composizione. E sì, i tratti sostituiscono DI senza bisogno di essere definiti nel punto di ingresso del codice. Questa è una delle cose che li rende preferibili secondo me.
Karl Bielefeldt,
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.