Perché dovrei voler usare le interfacce?

Capisco che ti costringano ad implementare metodi e simili, ma quello che non riesco a capire è il motivo per cui vorrai usarli. Qualcuno può darmi un buon esempio o una spiegazione sul perché vorrei implementarlo.

Un esempio specifico: le interfacce sono un buon modo per specificare un contratto che il codice di altre persone deve soddisfare.

Se sto scrivendo una libreria di codice, potrei scrivere codice che è valido per oggetti che hanno un certo insieme di comportamenti. La soluzione migliore è specificare quei comportamenti in un’interfaccia (nessuna implementazione, solo una descrizione) e quindi utilizzare riferimenti a oggetti che implementano quell’interfaccia nel mio codice libreria.

Quindi qualsiasi persona a caso può venire avanti, creare una class che implementa quell’interfaccia, creare un’istanza di un object di quella class e passarla al mio codice di libreria e aspettarsi che funzioni. Nota: è ovviamente ansible implementare rigorosamente un’interfaccia ignorando l’intenzione dell’interfaccia, quindi l’implementazione di un’interfaccia non garantisce che le cose funzionino. Stupido trova sempre un modo! 🙂

Un altro esempio specifico: due team che lavorano su diversi componenti che devono cooperare. Se i due team si siedono il giorno 1 e concordano su un insieme di interfacce, possono seguire i loro metodi separati e implementare i loro componenti attorno a tali interfacce. La Squadra A può build fasci di test che simulano il componente del Team B per il test e viceversa. Sviluppo parallelo e meno errori.

Il punto chiave è che le interfacce forniscono uno strato di astrazione in modo da poter scrivere codice che ignori i dettagli non necessari.

L’esempio canonico utilizzato nella maggior parte dei libri di testo è quello delle routine di ordinamento. È ansible ordinare qualsiasi class di oggetti purché si abbia un modo per confrontare due oggetti. È quindi ansible rendere qualsiasi class ordinabile implementando l’interfaccia IComparable , che ti obbliga ad implementare un metodo per confrontare due istanze. Tutte le routine di ordinamento vengono scritte per gestire i riferimenti agli oggetti IComparable, pertanto, non appena si implementa IComparable, è ansible utilizzare qualsiasi routine di ordinamento su raccolte di oggetti della class.

Un tipico esempio è un’architettura di plugin. Lo sviluppatore A scrive l’app principale e vuole accertarsi che tutti i plug-in scritti dagli sviluppatori B, C e D siano conformi a ciò che la sua app si aspetta da loro.

Le interfacce definiscono i contratti e questa è la parola chiave.

Si utilizza un’interfaccia quando è necessario definire un contratto nel programma, ma non ci si preoccupa del resto delle proprietà della class che adempie a quel contratto finché dura.

Quindi, vediamo un esempio. Supponiamo che tu abbia un metodo che fornisce la funzionalità per ordinare una lista. La prima cosa .. che cos’è una lista? Ti interessa davvero quali elementi contiene per ordinare la lista? La tua risposta dovrebbe essere no … In. NET (per esempio) hai un’interfaccia chiamata IList che definisce le operazioni che una lista DEVE supportare in modo che non ti interessi i dettagli reali sotto la superficie.

Tornando all’esempio, non si conosce davvero la class degli oggetti nell’elenco … né a te importa. Se puoi semplicemente confrontare l’object potresti anche ordinarli. Quindi dichiari un contratto:

 interface IComparable { // Return -1 if this is less than CompareWith // Return 0 if object are equal // Return 1 if CompareWith is less than this int Compare(object CompareWith); } 

quel contratto specifica che un metodo che accetta un object e restituisce un int deve essere implementato per essere comparabile. Ora hai definito un contratto e per ora non ti interessa l’object stesso ma il contratto, quindi puoi semplicemente:

 IComparable comp1 = list.GetItem(i) as IComparable; if (comp1.Compare(list.GetItem(i+1)) < 0) swapItem(list,i, i+1) 

PS: So che gli esempi sono un po 'ingenui ma sono esempi ...

Il modo più semplice per comprendere le interfacce è che consentono a oggetti diversi di esporre la funzionalità COMMON. Ciò consente al programmatore di scrivere molto più semplice, codice più corto che programma su un’interfaccia, quindi finché gli oggetti implementano quell’interfaccia funzionerà.

Esempio 1: Esistono molti provider di database, MySQL, MSSQL, Oracle, ecc. Tuttavia, tutti gli oggetti di database possono fare le stesse cose, quindi troverete molte interfacce per gli oggetti di database. Se un object implementa IDBConnection, espone i metodi Open () e Close (). Quindi, se voglio che il mio programma sia indipendente dal provider di database, programma l’interfaccia e non i provider specifici.

 IDbConnection connection = GetDatabaseConnectionFromConfig() connection.Open() // do stuff connection.Close() 

Vedi programmando su un’interfaccia (IDbconnessione) Ora posso SWAPare qualsiasi fornitore di dati nella mia configurazione, ma il mio codice rimane esattamente lo stesso. Questa flessibilità può essere estremamente utile e facile da mantenere. Il lato negativo di questo è che posso eseguire solo operazioni di database “generiche” e che non posso sfruttare appieno la forza offerta da ciascun fornitore in modo tale che con tutto ciò che si sta programmando si ha un compromesso e si deve determinare quale scenario sarà più vantaggioso.

Esempio 2: se si nota che quasi tutte le raccolte implementano questa interfaccia denominata IEnumerable. IEnumerable restituisce un IEnumerator che ha MoveNext (), Current e Reset (). Ciò consente a C # di spostarsi facilmente nella tua collezione. La ragione per cui può farlo è che espone l’interfaccia IEnumerable che CONOSCE che l’object espone i metodi necessari per attraversarlo. Questo fa due cose. 1) foreach loops ora saprà come enumerare la raccolta e 2) ora puoi applicare potenti espression LINQ alla tua collezione. Ancora una volta il motivo per cui le interfacce sono così utili qui è perché tutte le collezioni hanno qualcosa in COMMON, possono essere spostate. Ogni collezione può essere spostata in un modo diverso (elenco collegato vs array) ma questa è la bellezza delle interfacce è che l’implementazione è nascosta e irrilevante per il consumatore dell’interfaccia. MoveNext () ti dà il prossimo object della collezione, non importa come lo fa. Abbastanza carino, eh?

Esempio 3: Quando si progettano le proprie interfacce, è sufficiente porsi una domanda. Cosa hanno in comune queste cose? Una volta trovate tutte le cose che condividono gli oggetti, si astraggono tali proprietà / metodi in un’interfaccia in modo che ogni object possa ereditarsene. Quindi puoi programmare contro diversi oggetti usando un’unica interfaccia.

E naturalmente devo dare il mio esempio polimorfico C ++ preferito, l’esempio degli animali. Tutti gli animali condividono determinate caratteristiche. Diciamo che possono muoversi, parlare e hanno tutti un nome. Da quando ho appena identificato ciò che tutti i miei animali hanno in comune e posso astrarre quelle qualità nell’interfaccia IAnimal. Quindi creo un object Bear, un object Owl e un object Snake che implementano questa interfaccia. Il motivo per cui è ansible memorizzare diversi oggetti insieme che implementano la stessa interfaccia è perché le interfacce rappresentano una sostituzione di IS-A. Un orso è un animale, un gufo è un animale, così da quando posso raccoglierli tutti come animali.

 var animals = new IAnimal[] = {new Bear(), new Owl(), new Snake()} // here I can collect different objects in a single collection because they inherit from the same interface foreach (IAnimal animal in animals) { Console.WriteLine(animal.Name) animal.Speak() // a bear growls, a owl hoots, and a snake hisses animal.Move() // bear runs, owl flys, snake slithers } 

Potete vedere che anche se questi animali eseguono ciascuna azione in un modo diverso, posso programmare contro di loro tutti in un modello unificato e questo è solo uno dei molti vantaggi delle interfacce.

Quindi, ancora una volta la cosa più importante con le interfacce è ciò che gli oggetti hanno in comune, in modo da poter programmare contro gli oggetti DIVERSI in modo SAME. Risparmia tempo, crea applicazioni più flessibili, nasconde complessità / implementazione, modella oggetti / situazioni del mondo reale, tra molti altri vantaggi.

Spero che questo ti aiuti.

I pedali su una macchina implementano un’interfaccia. Vengo dagli Stati Uniti, dove guidiamo sul lato destro della strada. I nostri volanti sono sul lato sinistro dell’auto. I pedali per una trasmissione manuale da sinistra a destra sono frizione -> freno -> acceleratore. Quando sono andato in Irlanda, la guida è invertita. I volanti delle auto sono sulla destra e guidano sul lato sinistro della strada … ma i pedali, i pedali … hanno implementato la stessa interfaccia … tutti e tre i pedali erano nello stesso ordine … quindi anche se la class era diversa e la rete su cui operava la class era diversa, ero ancora a mio agio con l’interfaccia del pedale. Il mio cervello è stato in grado di chiamare i miei muscoli su questa macchina proprio come ogni altra macchina.

Pensa alle numerose interfacce non di programmazione di cui non possiamo vivere senza. Quindi rispondi alla tua stessa domanda.

Le interfacce sono assolutamente necessarie in un sistema orientato agli oggetti che si aspetta di fare buon uso del polimorfismo.

Un classico esempio potrebbe essere IVehicle, che ha un metodo Move (). Potresti avere classi Car, Bike e Tank, che implementano IVehicle. Possono tutto Move (), e si potrebbe scrivere codice a cui non importava che tipo di veicolo si stesse occupando, solo così può Move ().

 void MoveAVehicle(IVehicle vehicle) { vehicle.Move(); } 

Le interfacce sono una forma di polimorfismo. Un esempio:

Supponiamo di voler scrivere del codice di registrazione. Il logging andrà da qualche parte (magari su un file, o su una porta seriale sul dispositivo su cui il codice principale viene eseguito, o su un socket, o gettato via come / dev / null). Non sai dove: l’utente del tuo codice di registrazione deve essere libero di determinarlo. In effetti, il tuo codice di registrazione non interessa. Vuole solo qualcosa su cui scrivere byte.

Quindi, hai inventato un’interfaccia chiamata “qualcosa a cui puoi scrivere byte”. Al codice di registrazione viene fornita un’istanza di questa interfaccia (forse in fase di esecuzione, forse è configurata in fase di compilazione. È ancora polimorfismo, solo diversi tipi). Si scrivono una o più classi che implementano l’interfaccia e si può facilmente modificare il punto in cui la registrazione va semplicemente cambiando quale verrà utilizzato dal codice di registrazione. Qualcun altro può cambiare il percorso di registrazione scrivendo le proprie implementazioni dell’interfaccia, senza modificare il codice. Questo è fondamentalmente ciò che il polimorfismo equivale a: conoscere abbastanza su un object per usarlo in un modo particolare, pur consentendo di variare in tutti gli aspetti di cui non hai bisogno di sapere. Un’interfaccia descrive le cose che devi sapere.

I descrittori di file di C sono fondamentalmente un’interfaccia “qualcosa che posso leggere e / o scrivere byte da e / o a”, e quasi ogni linguaggio tipizzato ha tali interfacce in agguato nelle sue librerie standard: flussi o altro. I linguaggi non tipizzati di solito hanno tipi informali (forse chiamati contratti) che rappresentano flussi. Quindi in pratica non hai quasi mai dovuto inventare questa particolare interfaccia tu stesso: usi ciò che ti dà la lingua.

La registrazione e gli stream sono solo un esempio: le interfacce si verificano ogni volta che è ansible descrivere in termini astratti ciò che un object dovrebbe fare, ma non si vuole legarlo ad una particolare implementazione / class / altro.

 When you need different classs to share same methods you use Interfaces. 

Ci sono una serie di motivi per farlo. Quando si utilizza un’interfaccia, si è pronti in futuro quando è necessario rifattorizzare / riscrivere il codice. È inoltre ansible fornire una sorta di API standardizzata per operazioni semplici.

Ad esempio, se si desidera scrivere un algoritmo di ordinamento come il quicksort, tutto ciò che serve per ordinare un elenco di oggetti è che è ansible confrontare efficacemente due degli oggetti. Se crei un’interfaccia, ad esempio ISortable, chi crea oggetti può implementare l’interfaccia ISortable e può utilizzare il tuo codice di ordinamento.

Se stai scrivendo un codice che utilizza una memoria di database e scrivi su un’interfaccia di archiviazione, puoi sostituire quel codice lungo la linea.

Le interfacce incoraggiano l’accoppiamento più flessibile del codice in modo da poter disporre di una maggiore flessibilità.

Immagina la seguente interfaccia di base che definisce un meccanismo CRUD di base:

 interface Storable { function create($data); function read($id); function update($data, $id); function delete($id); } 

Da questa interfaccia, puoi dire che qualsiasi object che lo implementa, deve avere funzionalità per creare, leggere, aggiornare ed eliminare dati. Ciò potrebbe avvenire tramite una connessione al database, un lettore di file CSV e un lettore di file XML o qualsiasi altro tipo di meccanismo che potrebbe voler utilizzare le operazioni CRUD.

Quindi, ora potresti avere qualcosa di simile al seguente:

 class Logger { Storable storage; function Logger(Storable storage) { this.storage = storage; } function writeLogEntry() { this.storage.create("I am a log entry"); } } 

Questo logger non si cura se si passa una connessione al database o qualcosa che manipola i file sul disco. Tutto quello che deve sapere è che può chiamare create () su di esso e funzionerà come previsto.

La seguente domanda che ne deriverà è quindi, se database e file CSV, ecc. Possono tutti memorizzare dati, non dovrebbero essere ereditati da un object Storable generico e quindi eliminare la necessità di interfacce? La risposta a questo è no … non tutte le connessioni al database potrebbero implementare le operazioni CRUD, e lo stesso vale per ogni lettore di file.

Le interfacce definiscono ciò che l’object è in grado di fare e il modo in cui è necessario utilizzarlo … non quello che è!

Come hai notato, le interfacce fanno bene quando vuoi forzare qualcuno a farlo in un certo formato.

Le interfacce sono buone quando i dati non in un certo formato possono significare fare ipotesi pericolose nel codice.

Ad esempio, al momento sto scrivendo un’applicazione che trasformsrà i dati da un formato all’altro. Voglio costringerli a mettere quei campi così so che esisteranno e avranno maggiori possibilità di essere correttamente implementati. Non mi interessa se un’altra versione viene fuori e non viene compilata per loro perché è più probabile che i dati siano richiesti comunque.

Le interfacce vengono utilizzate raramente a causa di ciò, poiché in genere è ansible formulare ipotesi o non è necessario che i dati facciano ciò che è necessario fare.

Un’interfaccia, definisce semplicemente l’ interfaccia . Successivamente, è ansible definire il metodo (su altre classi), che ha accettato le interfacce come parametri (o più precisamente, object che implementa tale interfaccia). In questo modo il tuo metodo può operare su una grande varietà di oggetti, la cui unica comunanza è che implementano tale interfaccia.

Primo, ti danno uno strato aggiuntivo di astrazione. Puoi dire “Per questa funzione, questo parametro deve essere un object che ha questi metodi con questi parametri”. E probabilmente vuoi anche impostare il significato di questi metodi, in termini in qualche modo astratti, eppure ti consente di ragionare sul codice. Nelle lingue a dattilo si ottiene gratuitamente. Non c’è bisogno di “interfacce” di syntax esplicite. Eppure probabilmente creerai ancora una serie di interfacce concettuali, qualcosa come i contratti (come in Design by Contract).

Inoltre, le interfacce sono talvolta utilizzate per scopi meno “puri”. In Java, possono essere utilizzati per emulare l’ereditarietà multipla. In C ++, puoi usarli per ridurre i tempi di compilazione.

In generale, riducono l’accoppiamento nel codice. È una buona cosa.

Il tuo codice potrebbe anche essere più semplice da testare in questo modo.

Diciamo che vuoi tenere traccia di una collezione di cose. Tali raccolte devono supportare un sacco di cose, come aggiungere e rimuovere elementi e verificare se un elemento è presente nella raccolta.

È quindi ansible specificare un’interfaccia ICollection con i metodi add (), remove () e contains ().

Codice che non ha bisogno di sapere quale tipo di collezione (List, Array, Hash-table, Red-black tree, ecc.) Potrebbe accettare oggetti che hanno implementato l’interfaccia e lavorare con loro senza conoscere il loro tipo effettivo.

In .Net, creo le classi base e le eredito da loro quando le classi sono in qualche modo correlate. Ad esempio, la Persona della class base potrebbe essere ereditata da Dipendente e Cliente. La persona potrebbe avere proprietà comuni come campi dell’indirizzo, nome, telefono e così via. Il dipendente potrebbe avere la proprietà del proprio dipartimento. Il cliente ha altre proprietà esclusive.

Dal momento che una class può ereditare solo da un’altra class in .Net, io uso le interfacce per ulteriori funzionalità condivise. A volte le interfacce sono condivise da classi altrimenti non correlate. L’utilizzo di un’interfaccia crea un contratto che gli sviluppatori sapranno essere condiviso da tutte le altre classi che lo implementano. Impongo anche a quelle classi di implementare tutti i suoi membri.

In un articolo del mio blog descrivo brevemente tre interfacce di scopi.

Le interfacce possono avere scopi diversi:

  • Fornire implementazioni diverse per lo stesso objective. L’esempio tipico è un elenco, che può avere diverse implementazioni per diversi casi di utilizzo delle prestazioni (LinkedList, ArrayList, ecc.).
  • Consenti modifica dei criteri. Ad esempio, una funzione di ordinamento può accettare un’interfaccia comparabile per fornire qualsiasi tipo di criterio di ordinamento, basato sullo stesso algoritmo.
  • Nascondi i dettagli di implementazione. Ciò rende anche più semplice per un utente leggere i commenti, poiché nel corpo dell’interfaccia ci sono solo metodi, campi e commenti, non ci sono lunghi blocchi di codice da saltare.

Ecco il testo completo dell’articolo: http://weblogs.manas.com.ar/ary/2007/11/

Le interfacce in C # sono anche estremamente utili per consentire il polimorfismo per le classi che non condividono le stesse classi di base. Significato, dal momento che non possiamo avere ereditarietà multipla, è ansible utilizzare le interfacce per consentire l’utilizzo di diversi tipi. È anche un modo per permetterti di esporre membri privati ​​da usare senza riflessioni (implementazione esplicita), quindi può essere un buon modo per implementare funzionalità mantenendo pulito il tuo modello di oggetti.

Per esempio:

 public interface IExample { void Foo(); } public class Example : IExample { // explicit implementation syntax void IExample.Foo() { ... } } /* Usage */ Example e = new Example(); e.Foo(); // error, Foo does not exist ((IExample)e).Foo(); // success 

Penso che tu abbia bisogno di capire bene i modelli di design, quindi vedi il potere.

Scopri i primi modelli di design

Ci sono molte altre risposte a una domanda simile qui: Interface vs Base Class

Il miglior codice Java che abbia mai visto definisce quasi tutti i riferimenti a oggetti come istanze di interfacce anziché istanze di classi. È un forte segno di codice di qualità progettato per flessibilità e cambiamento.