2009-08-26 15 views
11

Lavoro in un'azienda in cui alcuni richiedono una giustificazione per l'uso di un'interfaccia nel nostro codice (Visual Studio C# 3.5).Good Case for Interfaces

Vorrei chiedere un ragionamento su Iron Clad per cui sono necessarie le interfacce. (Il mio obiettivo è di PROVARE che le interfacce sono una parte normale della programmazione.)

Non ho bisogno di convincermi, ho solo bisogno di un buon argomento da usare per convincere gli altri.

Il tipo di argomento sto cercando si basa infatti, non confronto basato (vale a dire "perché la libreria .NET li usa" è basato il confronto.)

L'argomento contro di loro è così: se una classe è correttamente l'installazione (con i suoi membri pubblici e privati) quindi un'interfaccia è solo un overhead in più perché quelli che usano la classe sono limitati ai membri pubblici. Se è necessario disporre di un'interfaccia implementata da più di 1 classe, è sufficiente configurare l'ereditarietà/polimorfismo.

+1

Non capisco l'argomento contro ... –

+16

se hai bisogno di spiegare al tuo team perché le interfacce sono un buon standard di programmazione, allora dovresti trovare un nuovo lavoro..no offesa .. –

+1

Sono d'accordo con la ricerca nuovo commento di lavoro. – Finglas

risposta

26

Codice disaccoppiamento. Programmando le interfacce si disaccoppia il codice utilizzando l'interfaccia dal codice che implementa l'interfaccia. Ciò consente di modificare l'implementazione senza dover refactoring di tutto il codice che lo utilizza. Questo funziona in congiunzione con l'ereditarietà/polimorfismo, consentendo di utilizzare uno qualsiasi di una serie di possibili implementazioni in modo intercambiabile.

Test di simulazione e unità. I framework di simulazione sono più facilmente utilizzabili quando i metodi sono virtuali, che si ottengono di default con le interfacce. Questo è in realtà il più grande motivo per cui creo interfacce.

Definizione del comportamento che può essere applicato a molte classi diverse che consente loro di essere utilizzate in modo intercambiabile, anche quando non esiste una relazione (diversa dal comportamento definito) tra le classi. Ad esempio, una classe Cavallo e Bicicletta possono entrambi avere un metodo di Marcia. È possibile definire un'interfaccia IRideable che definisce il comportamento di Marcia e qualsiasi classe che utilizza questo comportamento può utilizzare un oggetto Cavallo o Bicicletta senza forzare un'eredità innaturale tra di essi.

+0

stavo per scrivere qualcosa del genere, ma non abbastanza eloquente. quindi lascio che qualcuno faccia meglio. Il disaccoppiamento è la parte più importante della programmazione che offre flessibilità, gestibilità e riutilizzabilità. –

+2

Come addendum al commento Horse/Bicycle, la cosa bella delle interfacce è la possibilità di generalizzare le operazioni sulla tua classe senza limitare l'ereditarietà: http://stackoverflow.com/questions/558152/asp-net-unfamiliar-with- interfacce/558342 # 558342 – Juliet

+0

Se una modifica all'implementazione implicava una nuova firma del metodo, l'aggiunta di tale metodo interrompe tutti i client che utilizzano l'interfaccia. Che tipo di disaccoppiamento è quello? Dovremmo mirare a una combinazione di interfaccia e classe astratta? – IrishChieftain

1

Se il tuo negozio esegue test automatici, le interfacce sono un grande vantaggio per l'iniezione delle dipendenze e la possibilità di testare un'unità di software da solo.

2
  • Test Driven Development
  • Unit Testing

Senza interfacce producono codice disaccoppiato sarebbe un dolore. La migliore pratica è codificare un'interfaccia anziché un'implementazione concreta. Le interfacce sembrano spazzatura all'inizio, ma una volta scoperti i benefici li userete sempre.

6

Per abilitare il test dell'unità della classe.

Per tenere traccia delle dipendenze in modo efficiente (se l'interfaccia non viene controllata e toccata, solo la semantica della classe può essere stata modificata).

Perché non c'è nessun sovraccarico di runtime.

Per abilitare l'iniezione di dipendenza.

... e forse perché è friggin '2009, non gli anni '70, e i moderni progettisti di linguaggi in realtà hanno un indizio su quello che stanno facendo?

Non è necessario che le interfacce vengano generate in ogni interfaccia di classe: solo quelle che sono centrali per il sistema e che sono soggette a modifiche e/o estensioni significative.

2
  • È possibile implementare più interfacce. Non puoi ereditare da più classi.
  • ..that's it. I punti che gli altri stanno facendo riguardo al disaccoppiamento del codice e allo sviluppo basato sui test non arrivano al nocciolo della questione perché puoi fare anche quelle cose con classi astratte.
+0

non necessariamente, le classi astratte devono essere utilizzate quando esiste una logica comune da condividere. Potrei avere 2 classi che implementano la stessa interfaccia ma hanno una struttura completamente diversa tutte insieme. –

+0

Perché preoccuparsi di una lezione astratta se si intende ignorare l'implementazione di base? Quindi le interfacce sono migliori per i test unitari. Le classi astratte hanno però il loro posto. – Finglas

+0

Sono d'accordo che le classi astratte ovviamente _should_ forniscono qualche implementazione, altrimenti dovrebbero essere interfacce. Ma sto rispondendo direttamente alla domanda. –

10

Oltre alle cose spiegate in altre risposte, interfaces permettono di simulare ereditarietà multipla in .NET che altrimenti non è consentito.

Ahimè, come qualcuno ha detto

tecnologia è dominata da due tipi di persone: quelli che capiscono quello che non riescono a gestire, e coloro che gestiscono ciò che non capiscono.

+2

Questo è un ottimo punto. Poiché .NET non supporta l'ereditarietà multipla, l'uso delle interfacce è praticamente obbligatorio. –

+0

Meta-Knight: questa è la linea del partito, ma non sono sicuro di crederci. Non puoi semplicemente strutturare le cose in modo diverso per non richiedere più superclassi in una sottoclasse? Diresti anche che "Poiché .NET non supporta la distribuzione multipla, scrivere il tuo dispatcher è praticamente obbligatorio"? Ci sono sempre sistemi di oggetti più potenti, ma non devi sempre emulare tutte le loro caratteristiche solo per fare qualcosa. – Ken

2

Le interfacce consentono di dichiarare un concetto che può essere condiviso tra più tipi (IEnumerable) consentendo a ciascuno di questi tipi di avere una propria gerarchia di ereditarietà.

In questo caso, quello che stiamo dicendo è "questa cosa può essere enumerata, ma non è la sua unica caratteristica di definizione".

Le interfacce consentono di prendere le decisioni minime necessarie quando si definiscono le capacità del responsabile dell'implementazione. Quando crei una classe invece di un'interfaccia, hai già dichiarato che il tuo concetto è solo di classe e non utilizzabile per le strutture. Prendi altre decisioni anche quando dichiari membri in una classe, come visibilità e virtualità.

Ad esempio, è possibile creare una classe astratta con tutti i membri astratti pubblici e che è molto simile a un'interfaccia, ma si è dichiarato tale concetto come sovrascrivibile in tutte le classi child, mentre non sarebbe stato necessario effettuare quella decisione se hai usato un'interfaccia.

Inoltre, semplificano il test dell'unità, ma non credo che sia un argomento valido, dal momento che è possibile creare un sistema senza test di unità (non consigliato).

4

Le interfacce e le classi astratte modellano cose diverse. Derivi da una classe quando hai una relazione ISA, quindi la classe base modella qualcosa di concreto. Implementa un'interfaccia quando la tua classe può eseguire una serie specifica di attività.

Pensate a qualcosa che è serializzabile, non ha senso (dal punto di vista del design/della modellazione) avere una classe base chiamata Serializable in quanto non ha senso dire qualcosa è A Serializable. Avere qualcosa implementare un'interfaccia Serializable ha più senso dire "questo è qualcosa che la classe può fare, non quello che la classe è"

+1

+1 per l'ultima frase da solo! –

0

Non capisco come sia il suo overhead in più.

Le interfacce forniscono flessibilità, codice gestibile e riusabilità. Codificando su un'interfaccia non è necessario preoccuparsi del codice di implementazione concretizzato o della logica della classe che si sta utilizzando. Aspetti solo un risultato. Molte classi hanno un'implementazione diversa per la stessa funzionalità (StreamWriter, StringWriter, XmlWriter) .. non è necessario preoccuparsi di come implementano la scrittura, è sufficiente chiamarla.

+3

È un overhead aggiuntivo se crei le tue interfacce, perché devi creare e mantenere più codice. Per ogni metodo che si desidera inserire nell'interfaccia, è necessario duplicare la firma del metodo. Quindi, ogni volta che è necessario modificare la firma del metodo, sarà necessario cambiarla in almeno due punti. – airportyh

+0

Qui stai supportando entrambi gli argomenti. StreamWriter e StringWriter sono implementazioni di una classe astratta (TextWriter); XmlWriter non è correlato ad eccezione di Object e IDisposable. –

+0

Sì, il punto è che non stavo pensando a come .NET lo implementa realmente. Stavo cercando di pensare a un rapido esempio di classi che condividono una funzionalità (Scrivi) ma le implementano in modo diverso .. –

1

Il problema dell'argomento dell'ereditarietà è che avrete una gigantesca classe di Dio o una gerarchia così profonda che vi farà girare la testa. Oltre a questo, finirai con i metodi su una classe che non ti serve o che non ha senso.

Vedo un sacco di "nessuna ereditarietà multipla" e, se è vero, probabilmente non metterà in fase il team perché è possibile avere più livelli di ereditarietà per ottenere ciò che vorrebbero.

Viene in mente un'implementazione IDisposable. Il tuo team avrebbe messo un metodo Dispose nella classe Object e lo avrebbe propagato attraverso il sistema indipendentemente dal fatto che avesse o meno un senso per un oggetto.

18

L'argomento contro di loro è così: se una classe è correttamente messa a punto (con i suoi soci pubblici e privati), quindi un'interfaccia è solo più in testa perché quelli che utilizzano la classe sono riservato ai soci pubblici . Se è necessario che l'interfaccia sia implementata con più di 1 classe, , è sufficiente impostare l'ereditarietà/il polimorfismo con .

Si consideri il seguente codice:

interface ICrushable 
{ 
    void Crush(); 
} 

public class Vehicle 
{ 
} 

public class Animal 
{ 
} 

public class Car : Vehicle, ICrushable 
{ 
    public void Crush() 
    { 
    Console.WriteLine("Crrrrrassssh"); 
    } 
} 

public class Gorilla : Animal, ICrushable 
{ 
    public void Crush() 
    { 
    Console.WriteLine("Sqqqquuuuish"); 
    } 
} 

Se la soluzione sia senso a tutti di stabilire una gerarchia di classi che si riferisce ai veicoli Animali, anche se entrambi possono essere schiacciati dalla mia gigantesca macchina di frantumazione?

+2

+1 per il bel esempio di sqishing gorilla – usr

1

Un'interfaccia dichiara un contratto a cui qualsiasi oggetto che lo implementa aderirà. Ciò rende la verifica della qualità del codice molto più semplice rispetto al tentativo di imporre una struttura scritta (non di codice) o verbale, nel momento in cui una classe è decorata con il riferimento all'interfaccia i requisiti/contratto sono chiari e il codice non verrà compilato fino a quando non l'avrai implementato quell'interfaccia completamente e tipo-sicuro.

Ci sono molti altri grandi ragioni per usare Interfacce (elencati qui), ma probabilmente non risuonano con la gestione del bene come un buon comunicato, vecchio stile 'qualità';)

1

Bene, la mia prima reazione è che se devi spiegare perché hai bisogno di interfacce, è comunque una battaglia in salita :)

Detto questo, a parte tutte le ragioni sopra menzionate, le interfacce sono l'unico modo per la programmazione liberamente accoppiata, architetture n-tier dove è necessario aggiornare/sostituire componenti al volo ecc. - nell'esperienza personale tuttavia era troppo esoterico un concetto per il capo del team di architettura con il risultato che vivevamo in dll hell - in il mondo .net non-meno!

1

Per favore perdonami per lo pseudo codice in anticipo!

Leggere i principi SOLID. Ci sono alcuni motivi nei principi SOLID per l'utilizzo delle interfacce. Le interfacce consentono di disaccoppiare le dipendenze dall'implementazione. Puoi fare un ulteriore passo avanti usando uno strumento come StructureMap per far sciogliere l'accoppiamento.

Dove si potrebbe essere utilizzato per

Widget widget1 = new Widget; 

Questo dice specificamente che si desidera creare una nuova istanza di Widget. Tuttavia, se lo fai all'interno di un metodo di un altro oggetto, stai dicendo che l'altro oggetto dipende direttamente dall'uso del Widget.Quindi potremmo quindi dire qualcosa come

public class AnotherObject 
{ 
    public void SomeMethod(Widget widget1) 
    { 
     //..do something with widget1 
    } 
} 

Siamo ancora legati all'uso di Widget qui. Ma almeno questo è più testabile in quanto possiamo iniettare l'implementazione di Widget in SomeMethod. Ora, se dovessimo utilizzare un'interfaccia, potremmo ulteriormente disaccoppiare le cose.

public class AnotherObject 
{ 
    public void SomeMethod(IWidget widget1) 
    { 
     //..do something with widget1 
    } 
} 

Si noti che stiamo ora non richiedere una specifica implementazione di widget, ma invece stiamo chiedendo per tutto ciò che è conforme alla iWidget interfaccia. Ciò significa che qualsiasi cosa potrebbe essere iniettata, il che significa che nell'uso quotidiano del codice potremmo iniettare una reale implementazione del Widget. Ma questo significa anche che quando vogliamo testare questo codice possiamo iniettare un falso/mock/stub (dipende dalla tua comprensione di questi termini) e testare il nostro codice.

Ma come possiamo continuare. Con l'uso di StructureMap possiamo disaccoppiare questo codice ancora di più. Con l'ultimo esempio di codice il nostro codice chiamante il mio aspetto qualcosa di simile

public class AnotherObject 
{ 
    public void SomeMethod(IWidget widget1) 
    { 
     //..do something with widget1 
    } 
} 

public class CallingObject 
{ 
    public void AnotherMethod() 
    { 
     IWidget widget1 = new Widget(); 
     new AnotherObject().SomeMethod(widget1); 
    } 
} 

Come si può vedere nel codice sopra abbiamo rimosso la dipendenza nel SomeMethod passando in un oggetto che è conforme alla iWidget. Ma nel CallingObject(). AnotherMethod abbiamo ancora la dipendenza. Possiamo usare StructureMap per rimuovere anche questa dipendenza!

[PluginFamily("Default")] 
public interface IAnotherObject 
{ 
    ... 
} 

[PluginFamily("Default")] 
public interface ICallingObject 
{ 
    ... 
} 

[Pluggable("Default")] 
public class AnotherObject : IAnotherObject 
{ 
    private IWidget _widget; 
    public AnotherObject(IWidget widget) 
    { 
     _widget = widget; 
    } 

    public void SomeMethod() 
    { 
     //..do something with _widget 
    } 
} 

[Pluggable("Default")] 
public class CallingObject : ICallingObject 
{ 
    public void AnotherMethod() 
    { 
     ObjectFactory.GetInstance<IAnotherObject>().SomeMethod(); 
    } 
} 

Si noti che non ci sono dove nel codice precedente stiamo istanziando un'implementazione effettiva di AnotherObject. Poiché tutto è cablato per StructurMap, possiamo consentire a StructureMap di passare nelle implementazioni appropriate a seconda di quando e dove viene eseguito il codice. Ora il codice è davvero flessibile in quanto possiamo specificare tramite configurazione o in modo programmatico in un test l'implementazione che vogliamo utilizzare. Questa configurazione può essere eseguita al volo o come parte di un processo di compilazione, ecc. Ma non deve essere cablata ovunque.

1

Le domande come questo non rispondono alla domanda relativa a un caso per interfacce.

Tuttavia Vi consigliamo di prendere la persona in questione a leggere ..

Head First Design Patterns

- Lee

3

interfacce non sono 'richiesta per' a tutti, si tratta di una decisione di progettazione. Penso che sia necessario convincere te stesso, perché, caso per caso, è utile utilizzare un'interfaccia, perché c'è un sovraccarico nell'aggiunta di un'interfaccia. D'altra parte, per contrastare l'argomento contro le interfacce perché si può 'semplicemente' usare l'ereditarietà: l'ereditarietà ha i suoi limiti, uno di questi è che - almeno in C# e Java - si può usare l'ereditarietà una sola volta (ereditarietà singola); ma il secondo - e forse più importante - è che l'ereditarietà richiede di capire il funzionamento non solo della classe genitore, ma di tutte le classi antenate, il che rende l'estensione più difficile ma anche più fragile, perché un cambiamento nella classe genitore ' l'implementazione potrebbe facilmente rompere le sottoclassi. Questo è il punto cruciale dell'argomento "composizione sull'ereditarietà" che il libro del GOF ci ha insegnato.

+0

Qual è il libro GOF? – Vaccano

+0

http://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional/dp/0201633612 – airportyh

3

Ti è stata fornita una serie di linee guida che i tuoi capi hanno ritenuto appropriate per il tuo luogo di lavoro e il tuo dominio problematico. Quindi, per essere persuasivi nel cambiare queste linee guida, non si tratta di dimostrare che le interfacce sono una cosa buona in generale, si tratta di dimostrare che ne hai bisogno nel tuo posto di lavoro.

Come dimostrare di aver bisogno di interfacce nel codice che scrivi sul posto di lavoro?Trovando un posto nella propria base di codice effettiva (non in qualche codice prodotto da qualcun altro, e certamente non in qualche esempio di esempio su Duck che implementa il metodo makeNoise in IAnimal) dove una soluzione basata su interfaccia è migliore di una soluzione basata sull'ereditarietà. Mostra ai tuoi capi il problema che stai affrontando e chiedi se ha senso modificare le linee guida per sistemare situazioni del genere. È un momento insegnabile in cui tutti guardano gli stessi fatti invece di colpire l'un l'altro con generalità e speculazioni.

Le linee guida sembrano essere guidate da una preoccupazione per evitare l'overengineering e la generalizzazione prematura. Quindi se fai una discussione sulla falsariga di dovremmo avere un'interfaccia qui nel caso in cui in futuro dovessimo ..., è ben intenzionato, ma per i tuoi capi mette in moto gli stessi campanelli di allarme che motivato la linea guida in primo luogo.

Attendi fino a quando non ci sia un buon caso oggettivo, vale sia per le tecniche di programmazione che usi nel codice di produzione, sia per le cose con le quali si mettono in discussione argomenti con i tuoi manager.