2009-08-05 30 views
30

Mentre alcune linee guida indicano che è necessario utilizzare un'interfaccia quando si desidera definire un contratto per una classe in cui l'ereditarietà non è chiara (IDomesticated) e l'ereditarietà quando la classe è un'estensione di un'altra (Cat : Mammal, Snake : Reptile), ci sono casi in cui (secondo me) queste linee guida entrano in un'area grigia.Quando utilizzare le interfacce o classi astratte? Quando usare entrambi?

Ad esempio, supponiamo che la mia implementazione sia stata Cat : Pet. Pet è una classe astratta. Dovrebbe essere esteso a Cat : Mammal, IDomesticated dove Mammal è una classe astratta e IDomesticated è un'interfaccia? O sono in conflitto con i principi KISS/YAGNI (anche se non sono sicuro se ci sarà una classe Wolf in futuro, che non sarebbe in grado di ereditare da Pet)?

Allontanarsi dalla metaforica Cat s e Pet s, diciamo che ho alcune classi che rappresentano le fonti per i dati in entrata. Tutti hanno bisogno di implementare la stessa base in qualche modo. Potrei implementare qualche codice generico in una classe astratta Source e ereditare da esso. Potrei anche creare un'interfaccia ISource (che mi sembra più "giusta") e ri-implementare il codice generico in ogni classe (che è meno intuitivo). Alla fine, potevo "avere la torta e mangiarla" facendo sia la classe astratta che l'interfaccia. Cosa c'è di meglio?

Questi due casi richiamano i punti per l'utilizzo solo di una classe astratta, solo un'interfaccia e utilizzando sia una classe astratta che un'interfaccia. Sono tutte scelte valide o ci sono "regole" per quando uno dovrebbe essere usato rispetto ad un altro?


vorrei precisare che per "utilizzando sia una classe astratta e un'interfaccia" che include il caso in cui essi rappresentano essenzialmente la stessa cosa (Source e ISource entrambi hanno gli stessi membri), ma la classe aggiunge funzionalità generiche mentre l'interfaccia specifica il contratto.

Vale anche la pena notare che questa domanda riguarda principalmente le lingue che non supportano l'ereditarietà multipla (come .NET e Java).

+1

Mi piacerebbe notare che ho visto diverse domande "Interface vs. Abstract classes", ma in questa domanda, sono più interessato a quando usare * both * interfacce e classi astratte (se questo è una cosa valida da fare.) – Blixt

+0

possibile duplicato di [Interface vs Abstract Class (OO generale)] (http://stackoverflow.com/questions/761194/interface-vs-abstract-class-general-oo) –

+0

possibile duplicato di [Quando utilizzare un'interfaccia invece di una classe astratta e viceversa?] (Http://stackoverflow.com/questions/479142/when-to-use-an-interface-instead-of-an-abstract-class-and -vice-versa) – nawfal

risposta

33

Come prima regola, preferisco le classi astratte su interfacce, based on the .NET Design Guidelines. Il ragionamento si applica molto più ampio di .NET, ma è meglio spiegato nel libro Framework Design Guidelines.

Il motivo principale alla base della preferenza per le classi di base astratte è il controllo delle versioni, poiché è sempre possibile aggiungere un nuovo membro virtuale a una classe di base astratta senza rompere i client esistenti. Questo non è possibile con le interfacce.

Ci sono scenari in cui un'interfaccia è ancora la scelta corretta (in particolare quando non ti interessa il controllo delle versioni), ma essere consapevole dei vantaggi e degli svantaggi ti consente di prendere la decisione giusta.

Così come una risposta parziale prima di continuare: Avere sia un'interfaccia che una classe base ha senso solo se si decide di codificare un'interfaccia in primo luogo. Se si consente un'interfaccia, è necessario codificare solo quella interfaccia, poiché altrimenti si violerebbe il Principio di sostituzione di Liskov. In altre parole, anche se fornisci una classe base che implementa l'interfaccia, non puoi lasciare che il tuo codice consumi quella classe base.

Se si decide di eseguire il codice su una classe base, l'interfaccia non ha senso.

Se si decide di eseguire il codice su un'interfaccia, disporre di una classe base che fornisce funzionalità predefinite è facoltativa. Non è necessario, ma potrebbe velocizzare le cose per gli implementatori, quindi puoi fornirne uno a titolo di cortesia.

Un esempio che viene in mente è in ASP.NET MVC. La pipeline di richieste funziona su IController, ma esiste una classe di base Controller che in genere si utilizza per implementare il comportamento.

Risposta finale: se si utilizza una classe base astratta, utilizzare solo quella. Se si utilizza un'interfaccia, una classe base è una cortesia opzionale per gli implementatori.


Aggiornamento: Hopiù preferiscono le classi astratte su interfacce, e non ho per molto tempo; preferisco invece la composizione sull'ereditarietà, usando SOLID come linea guida.

(Mentre potevo modificare direttamente il testo di cui sopra, cambierebbe radicalmente la natura del post, e dal momento che poche persone lo hanno trovato abbastanza prezioso da poterlo votare, preferirei lasciare il testo originale in piedi, e invece aggiungere questa nota. l'ultima parte del post è ancora significativo, quindi sarebbe un peccato per eliminarlo, anche.)

+0

Ah grazie per i consigli utili! Ritengo che le interfacce siano dette "buone pratiche" (e quindi cerco di trovarne gli usi), ma in pratica non risolvono veramente molti problemi, almeno non in progetti di piccole e medie dimensioni ... Credo che cercherò di seguire le lezioni di base astratte nel mio prossimo progetto a meno che non trovo una buona ragione per andare con un'interfaccia. – Blixt

+2

Definire un'interfaccia e una classe astratta che la implementa. Quindi la maggior parte delle implementazioni può semplicemente estendere la classe astratta senza problemi di versioning (nuovi metodi possono essere aggiunti all'interfaccia e alla classe astratta), mentre se ciò non è possibile (ad es. Quando c'è già un altro super) un'implementazione può semplicemente implementare l'interfaccia. –

+0

Con JDK8 e i metodi predefiniti per le interfacce l'argomento versioning appare ancora meno rilevante ora. Tuttavia una distinzione interessante a cui pensare. – mibollma

2

Io uso sempre queste linee guida:

  • utilizzare le interfacce per i molteplici tipo di ereditarietà (come .NET/Java non usare l'ereditarietà multipla)
  • Utilizzare le classi astratte per un'implementazione riutilizzabile di un tipo

La regola della preoccupazione dominante indica che una classe ha sempre un problema principale e 0 o più altri (vedere http://citeseer.ist.psu.edu/tarr99degrees.html). Quelle 0 o più altre che poi implementate attraverso le interfacce, in quanto la classe implementa quindi tutti i tipi che deve implementare (la sua e tutte le interfacce implementate).

In un mondo di multipla ereditarietà dell'implementazione (ad esempio C++/Eiffel), si erediterebbe dalle classi che implementano le interfacce. (In teoria, potrebbe non funzionare altrettanto bene.)

2

Se si desidera fornire l'opzione di sostituire completamente l'implementazione, utilizzare un'interfaccia. Questo vale soprattutto per le interazioni tra i componenti principali, che dovrebbero sempre essere disaccoppiati dalle interfacce.

Ci possono anche essere motivi tecnici per preferire un'interfaccia, ad esempio per abilitare il mocking nei test di unità.

All'interno di un componente può essere utile utilizzare semplicemente una classe astratta per accedere a una gerarchia di classi.

Se si utilizza un'interfaccia e si dispone di una gerarchia di classi di implementazione, è buona norma avere una classe astratta che contenga le parti comuni dell'implementazione. Per esempio.

interface Foo 
abstract class FooBase implements Foo 
class FunnyFoo extends FooBase 
class SeriousFoo extends FooBase 

Si potrebbe anche avere più classi astratte che si ereditano l'una dall'altra per una gerarchia più complicata.

2

C'è anche qualcosa chiamato il principio DRY - Do not Repeat Yourself.

Nell'esempio di origini dati si dice che esiste un codice generico comune tra diverse implementazioni. Per me sembra che il modo migliore per gestirlo sia avere una classe astratta con il codice generico e alcune classi concrete che la estendono.

Il vantaggio è che ogni correzione di bug nel codice generico va a vantaggio di tutte le implementazioni concrete.

Se si passa all'interfaccia, è necessario conservare diverse copie dello stesso codice che richiede problemi.

Per quanto riguarda l'interfaccia + astratta se non esiste una giustificazione immediata, non lo farei. Estrarre l'interfaccia dalla classe astratta è un facile refactoring, quindi lo farei solo quando è effettivamente necessario.

+0

La maggior parte degli scenari in cui si dispone di un'interfaccia non comporterà "più copie dello stesso codice". – RichardOD

+0

In questo specifico scenario in base all'OP, ha un codice comune. Se si utilizzano le interfacce, è possibile averlo ancora in una posizione utilizzando una delega, ma non mi preoccuperei. –

19

Io tendo ad usare classi base astratte (o meno) per descrivere ciò che qualcosa è , mentre io uso le interfacce per descrivere le capacità di un oggetto.

Un gatto è un mammifero, ma uno dei suoi capacità è che è Pettable.

Oppure, per dirla in altro modo, le classi sono nomi, mentre le interfacce si avvicinano agli aggettivi.

12

da MSDN, Recommendations for Abstract Classes vs. Interfaces

  • Se si prevede la creazione di versioni multiple del tuo componente, crea una classe astratta. Le classi astratte forniscono un modo semplice e facile per la versione dei componenti. Aggiornando la classe base, tutte le classi ereditanti vengono automaticamente aggiornate con la modifica. Le interfacce, d'altra parte, non possono essere modificate una volta create. Se è necessaria una nuova versione di un'interfaccia, è necessario creare un'intera nuova interfaccia.

  • Se la funzionalità che si sta creando sarà utile in un'ampia gamma di oggetti disparati, utilizzare un'interfaccia. Le classi astratte dovrebbero essere utilizzate principalmente per oggetti strettamente correlati, mentre le interfacce sono più adatte per fornire funzionalità comuni a classi non correlate.

  • Se si progettano piccole funzionalità concise, utilizzare le interfacce. Se stai progettando unità funzionali di grandi dimensioni, usa una classe astratta.

  • Se si desidera fornire funzionalità comuni e implementate tra tutte le implementazioni del componente, utilizzare una classe astratta. Le classi astratte consentono di implementare parzialmente la classe, mentre le interfacce non contengono alcuna implementazione per i membri.

0

Fare riferimento al di sotto di SE domanda per le linee guida generiche:

Interface vs Abstract Class (general OO)

caso d'uso pratico per l'interfaccia:

  1. Attuazione Strategy_pattern: Definire la strategia un'interfaccia Cambia l'implementazione in modo dinamico con una delle implementazioni concrete della strategia in fase di esecuzione.

  2. Definire una capacità tra tra più classi non correlate.

caso pratico utilizzo per la classe astratta:

  1. Attuazione Template_method_pattern: Definire uno scheletro di un algoritmo. Le classi figlie non possono cambiare la strucutre dell'algortihm ma possono ridefinire una parte dell'implementazione nelle classi figlie.

  2. Quando si desidera condividere le variabili non statiche e non finali tra più classi correlate con "ha una relazione".

uso di entrambi classe abstradt e l'interfaccia:

Se si sta andando per una classe astratta, è possibile spostare i metodi astratti di interfacciarsi e di classe astratta può semplicemente implementare tale interfaccia. Tutti i casi d'uso di classi astratte possono rientrare in questa categoria.

Problemi correlati