Perché le interfacce sono utili?

154

Ho studiato e codificato in C # da qualche tempo. Ma ancora, non riesco a capire l'utilità delle interfacce. Portano troppo poco al tavolo. Oltre a fornire le firme della funzione, non fanno nulla. Se riesco a ricordare i nomi e la firma delle funzioni che è necessario implementare, non c'è bisogno di loro. Sono lì solo per assicurarsi che le suddette funzioni (nell'interfaccia) siano implementate nella classe ereditaria.

C # è un ottimo linguaggio, ma a volte ti dà la sensazione che prima Microsoft crei il problema (non permettendo l'ereditarietà multipla) e quindi fornisca la soluzione, che è piuttosto noiosa.

Questa è la mia comprensione che si basa su un'esperienza di codifica limitata. Qual è la tua opinione sulle interfacce? Quante volte ne fai uso e cosa ti fa fare così?

    
posta Pankaj Upadhyay 21.09.2011 - 21:18
fonte

19 risposte

149

They are there just to make sure that the said functions (in the interface) are implemented in the inheriting class.

Una corretta. Questo è un vantaggio sufficientemente impressionante per giustificare la funzionalità. Come altri hanno già detto, un'interfaccia è un obbligo contrattuale per implementare determinati metodi, proprietà ed eventi. Il vantaggio irresistibile di un linguaggio tipizzato staticamente è che il compilatore può verificare che un contratto su cui si basa il tuo codice sia effettivamente rispettato.

Detto questo, le interfacce sono un modo abbastanza debole per rappresentare gli obblighi contrattuali. Se si desidera un modo più strong e flessibile per rappresentare gli obblighi contrattuali, consultare la funzione Contratti di codice fornita con l'ultima versione di Visual Studio.

C# is a great language, but sometime it gives you the feeling that first Microsoft creates the problem(not allowing multiple inheritance) and then provides the solution, which is rather a tedious one.

Bene, sono contento che ti piaccia.

Tutti i progetti software complessi sono il risultato del confronto tra le funzionalità in conflitto tra loro e il tentativo di trovare il "punto debole" che offre grandi vantaggi a costi contenuti. Abbiamo appreso attraverso la dolorosa esperienza che i linguaggi che consentono l'ereditarietà multipla ai fini della condivisione dell'implementazione hanno vantaggi relativamente piccoli e costi relativamente elevati. Consentendo l'ereditarietà multipla solo sulle interfacce, che non condividono i dettagli di implementazione, offre molti dei vantaggi dell'ereditarietà multipla senza la maggior parte dei costi.

    
risposta data 14.09.2011 - 17:09
fonte
228

Quindi in questo esempio, PowerSocket non sa nient'altro degli altri oggetti. Gli oggetti dipendono tutti dal Potere fornito da PowerSocket, quindi implementano IPowerPlug e in questo modo possono collegarsi ad esso.

Le interfacce sono utili perché forniscono contratti che gli oggetti possono utilizzare per lavorare insieme senza dover conoscere nient'altro l'uno dell'altro.

    
risposta data 16.09.2011 - 02:54
fonte
144

Other than providing the signatures of function, they do nothing. If I can remember the names and signature of the functions which are needed to be implemented, there is no need for them

Il punto delle interfacce non è quello di aiutarti a ricordare quale metodo implementare, è qui per definire un contratto . In foreach esempio di P.Brian.Mackey (che risulta errato , ma non ci interessa), IEnumerable definisce un contratto tra foreach e qualsiasi cosa enumerabile. Dice: "Chiunque tu sia, a patto che tu rispetti il contratto (implementa IEnumerable), ti prometto che itererò su tutti i tuoi elementi". E questo è fantastico (per un linguaggio non dinamico).

Grazie alle interfacce è possibile ottenere un accoppiamento molto basso tra due classi.

    
risposta data 23.05.2017 - 14:40
fonte
37

Le interfacce sono il modo migliore per mantenere i costrutti ben disaccoppiati.

Durante la scrittura dei test, scoprirai che le lezioni concrete non funzioneranno nel tuo ambiente di test.

Esempio: vuoi testare una classe che dipende da una classe Data Access Service . Se questa classe parla con un servizio Web o un database, il test dell'unità non verrà eseguito nell'ambiente di test (inoltre è diventato un test di integrazione).

Soluzione? Utilizza un'interfaccia per il tuo Servizio di accesso ai dati e Metti quell'interfaccia in modo da poter testare la tua classe come unità.

D'altra parte, WPF & Silverlight non gioca affatto con le interfacce quando si tratta di rilegare. Questa è una ruga piuttosto brutta.

    
risposta data 14.09.2011 - 16:40
fonte
29

Le interfacce sono la spina dorsale del polimorfismo (statico)! L'interfaccia è ciò che conta. L'ereditarietà non funzionerebbe senza interfacce, poiché le sottoclassi ereditano fondamentalmente l'interfaccia già implementata del genitore.

How often you make uses of them and what makes you do so ??

Abbastanza spesso. Tutto ciò che deve essere inseribile è un'interfaccia nelle mie applicazioni. Spesso si hanno classi altrimenti non correlate che devono fornire lo stesso comportamento. Non puoi risolvere questi problemi con l'ereditarietà.

Hai bisogno di algoritmi diversi per eseguire operazioni sugli stessi dati? Utilizza un'interfaccia ( vedi schema di strategia )!

Vuoi utilizzare diverse implementazioni di elenchi? Codice contro un'interfaccia e il chiamante non ha bisogno di preoccuparsi dell'implementazione!

È stato considerato una buona pratica (non solo in OOP) codificare le interfacce per anni, per una sola ragione: è facile cambiare un'implementazione quando ci si rende conto che non si adatta alle proprie esigenze. È piuttosto complicato se si tenta di ottenere ciò solo con un'eredità multipla o si riduce alla creazione di classi vuote per fornire l'interfaccia necessaria.

    
risposta data 30.09.2011 - 19:14
fonte
12

Probabilmente hai usato foreach e hai trovato che è uno strumento di iterazione piuttosto utile. Sapevi che richiede un'interfaccia per funzionare, IEnumerable ?

Questo è certamente un caso concreto che parla dell'utilità di un'interfaccia.

    
risposta data 14.09.2011 - 16:06
fonte
11

Le interfacce riguardano la codifica di oggetti come una spina per il cablaggio domestico. Vuoi saldare la radio direttamente al cablaggio della tua casa? E il tuo aspirapolvere? Ovviamente no. La spina e la presa in cui si inserisce formano l'interfaccia tra il cablaggio della casa e il dispositivo che ne ha bisogno. Il cablaggio della vostra casa non deve sapere nulla del dispositivo se non utilizzando una spina a tre poli con messa a terra e richiede energia elettrica a 120 V ca < = 15 A. Al contrario, il dispositivo non richiede alcuna conoscenza arcana di come la tua casa è cablata, a parte il fatto che ha una o più prese a tre poli posizionate convenientemente che forniscono 120VAC < = 15A.

Le interfacce svolgono una funzione molto simile nel codice. Un oggetto può dichiarare che una particolare variabile, parametro o tipo di ritorno è di un tipo di interfaccia. L'interfaccia non può essere istanziata direttamente con una parola chiave new , ma il mio oggetto può essere dato, o trovare, l'implementazione di quell'interfaccia con cui dovrà lavorare. Una volta che l'oggetto ha la sua dipendenza, non deve sapere esattamente quale sia questa dipendenza, deve solo sapere che può chiamare i metodi X, Y e Z sulla dipendenza. Le implementazioni dell'interfaccia non devono sapere come saranno utilizzate, devono solo sapere che ci si aspetta che forniscano metodi X, Y e Z con particolari firme.

Quindi, astrarre più oggetti dietro la stessa interfaccia, fornisce un insieme comune di funzionalità a qualsiasi utente di oggetti di quell'interfaccia. Non è necessario sapere che l'oggetto è, ad esempio, un elenco, un dizionario, una lista collegata, una lista ordinata o qualsiasi altra cosa. Poiché si sa che tutti questi sono IEnumerables, è possibile utilizzare i metodi di IEnumerable per scorrere ciascuno di questi elementi uno alla volta. Non è necessario sapere che una classe di output è un ConsoleWriter, un FileWriter, un NetworkStreamWriter o anche un MulticastWriter che accetta altri tipi di writer; tutto quello che devi sapere è che sono tutti I Scrittori (o qualsiasi altra cosa), e quindi hanno un metodo "Scrivi" in cui puoi passare una stringa, e quella stringa verrà emessa.

    
risposta data 09.07.2012 - 18:45
fonte
7

Anche se è chiaro che il programmatore (in primo luogo, almeno) abbia ereditarietà multipla, si tratta di un'omissione quasi banale e si dovrebbe (nella maggior parte dei casi) non fare affidamento sull'ereditarietà multipla. Le ragioni di questo sono complesse, ma se veramente vuoi saperne di più, considera l'esperienza dei due più famosi (da indice TIOBE ) linguaggi di programmazione che lo supportano: C ++ e Python (3 ° e 8 ° rispettabilmente).

In Python, l'ereditarietà multipla è supportata, eppure è quasi universalmente fraintesa dai programmatori e per affermare che sai come funziona, significa leggere e comprendere questo documento sull'argomento: Ordine di risoluzione del metodo . Qualcos'altro, quello che è successo in Python, è che le interfacce sono entrate nel linguaggio - Zope.Interfaces.

Per C ++, google "gerarchia dei diamanti C ++" ed ecco la bruttezza che sta per coprirti. I professionisti di C ++ sanno come utilizzare l'ereditarietà multipla. Tutti gli altri di solito si limitano a giocare senza sapere quali saranno i risultati. Un'altra cosa che mostra quanto siano utili le interfacce è il fatto che in molti casi una classe potrebbe dover sovrascrivere completamente il comportamento dei suoi genitori. In tali casi, l'implementazione genitore non è necessaria e grava sulla classe figlia solo con la memoria delle variabili private del genitore, che potrebbero non avere importanza nell'età C #, ma è importante quando si esegue la programmazione incorporata. Se si utilizza un'interfaccia, il problema è inesistente.

In conclusione, le interfacce sono, a mio parere, una parte essenziale dell'OOP, perché impongono un contratto. L'ereditarietà multipla è utile in casi limitati, e di solito solo per ragazzi che sanno come usarlo. Quindi, se sei un principiante, sei quello che viene trattato dalla mancanza di eredità multipla - questo ti dà una migliore possibilità di di non commettere errori .

Inoltre, storicamente, l'idea di un'interfaccia è radicata molto prima rispetto alle specifiche di progettazione C # di Microsoft. La maggior parte delle persone considera C # come un aggiornamento su Java (nella maggior parte dei casi), e indovina dove C # ha ottenuto le sue interfacce - Java. Il protocollo è una parola precedente per lo stesso concetto ed è molto più vecchio di .NET.

Aggiornamento: Ora vedo che avrei potuto rispondere a una domanda diversa - perché interfacce invece ereditarietà multipla, ma questa sembrava la risposta che stavi cercando. Oltre a un linguaggio OO dovrebbe avere almeno uno dei due, e le altre risposte hanno coperto la tua domanda originale.

    
risposta data 14.09.2011 - 17:22
fonte
6

Per me è difficile immaginare un codice C # pulito e orientato agli oggetti senza l'uso di interfacce. Li usi ogni volta che desideri imporre la disponibilità di determinate funzionalità senza forzare le classi ad ereditare da una specifica classe base, e ciò consente al tuo codice di avere il relativo livello di (basso) accoppiamento.

Non sono d'accordo sul fatto che l'ereditarietà multipla sia migliore delle interfacce, anche prima di arrivare a sostenere che l'ereditarietà multipla si accompagna al proprio insieme di dolori. Le interfacce sono uno strumento di base per abilitare il polimorfismo e il riutilizzo del codice, cos'altro è necessario?

    
risposta data 14.09.2011 - 16:36
fonte
5

Personalmente amo la classe astratta e la uso più di un'interfaccia. La principale differenza consiste nell'integrazione con interfacce .NET come IDisposable, IEnumerable e così via ... e con l'interoperabilità COM. Inoltre, l'interfaccia è un po 'meno fatica da scrivere rispetto a una classe astratta, e una classe può implementare più di un'interfaccia mentre può ereditare solo da una classe.

Detto questo, trovo che le maggior parte delle cose che userei un'interfaccia per essere meglio servite da una classe astratta. Pure funzioni virtuali - funzioni astratte - consentono di forzare un implementatore a definire una funzione simile al modo in cui un'interfaccia costringe un implementatore a definire tutti i suoi membri.

Tuttavia, in genere utilizzi un'interfaccia quando non vuoi imporre un determinato design alla super classe, mentre utilizzi una classe astratta per avere un progetto riutilizzabile che è già in gran parte implementato.

Ho utilizzato estesamente interfacce con gli ambienti dei plug-in di scrittura utilizzando lo spazio dei nomi System.ComponentModel. Sono abbastanza utili.

    
risposta data 14.09.2011 - 16:25
fonte
5

Posso dire che mi relaziono a questo. Anche quando ho iniziato a conoscere OO e C #, anch'io non ho avuto interfacce. Va bene. Abbiamo solo bisogno di venire a contatto di qualcosa che ti faccia apprezzare le convenienze delle interfacce.

Lasciami provare due approcci. E perdonami per le generalizzazioni.

Prova 1

Supponiamo che tu sia un madrelingua inglese. Vai in un altro paese in cui l'inglese non è la lingua madre. Hai bisogno di aiuto. Hai bisogno di qualcuno che ti possa aiutare.

Chiedete: "Ehi, sei nato negli Stati Uniti?" Questa è l'ereditarietà.

O chiedi: "Ehi, parli inglese"? Questa è l'interfaccia.

Se ti interessa ciò che fa, puoi fare affidamento sulle interfacce. Se ti interessa ciò che è, fai affidamento sull'eredità.

Va bene affidarsi all'eredità. Se hai bisogno di qualcuno che parli inglese, che ami il tè e che ami il calcio, ti conviene chiedere un inglese. :)

Prova 2

Ok, proviamo con un altro esempio.

Usi diversi database e devi implementare classi astratte per lavorare con loro. Passerai la tua classe ad una classe dal fornitore di DB.

public abstract class SuperDatabaseHelper
{
   void Connect (string User, string Password)
}

public abstract class HiperDatabaseHelper
{
   void Connect (string Password, string User)
}

Molteplici ereditarietà, dici? Prova con il caso precedente. Non puoi Il compilatore non saprà quale metodo Connect stai cercando di chiamare.

interface ISuperDatabaseHelper
{
  void Connect (string User, string Password)
}

interface IHiperDatabaseHelper
{
   void Connect (string Password, string User)
}

Ora, c'è qualcosa su cui possiamo lavorare - almeno in C # - dove possiamo implementare le interfacce in modo esplicito.

public class MyDatabaseHelper : ISuperDatabaseHelper, IHiperDatabaseHelper
{
   IHiperDataBaseHelper.Connect(string Password, string User)
   {
      //
   }

   ISuperDataBaseHelper.Connect(string User, string Password)
   {
      //
   }

}

Conclusione

Gli esempi non sono dei migliori, ma penso che riescano a raggiungere il punto.

Potrai solo "ottenere" le interfacce quando ne senti il bisogno. Fino a quando non penserai che non sono per te.

    
risposta data 01.12.2012 - 14:36
fonte
4

Ci sono 2 motivi principali:

  1. Mancanza di ereditarietà multipla. È possibile ereditare da una classe base e implementare qualsiasi numero di interfacce. Questo è l'unico modo per "fare" l'ereditarietà multipla in .NET.
  2. Interoperabilità COM. Tutto ciò che dovrà essere utilizzato dalle tecnologie "più vecchie" dovrà definire le interfacce.
risposta data 05.04.2012 - 12:20
fonte
3

L'uso delle interfacce aiuta un sistema a rimanere disaccoppiato e quindi più semplice da rifattorizzare, modificare e ridistribuire. È un concetto molto centrale all'ortodossia orientata agli oggetti e ho imparato a conoscerlo quando i guru del C ++ hanno realizzato "classi astratte pure" che sono abbastanza equivalenti alle interfacce.

    
risposta data 14.09.2011 - 16:27
fonte
3

Le interfacce da sole non sono molto utili. Ma quando implementato da classi concrete, vedi che ti dà la flessibilità di avere una o più implementazioni. Il vantaggio è che l'oggetto che usa l'interfaccia non ha bisogno di sapere come vanno i dettagli dell'attuazione reale - si chiama incapsulamento.

    
risposta data 14.09.2011 - 20:27
fonte
2

Sono principalmente utilizzati per la riusabilità del codice. Se si esegue il codice nell'interfaccia, è possibile utilizzare una classe diversa che eredita da tale interfaccia e non interrompere tutto.

Inoltre sono molto utili nei servizi web in cui si desidera far sapere al client che cosa fa una classe (in modo che possano consumarla), ma non si vuole dare loro il codice effettivo.

    
risposta data 14.09.2011 - 16:02
fonte
2

Da giovane programmatore / sviluppatore, imparando solo C # potresti non vedere l'utilità dell'interfaccia, perché potresti scrivere i tuoi codici usando le tue classi e il codice funziona bene, ma nello scenario reale, costruendo uno scalabile, robusto e mantenibile l'applicazione comporta l'uso di alcuni elementi architettonici e di pattern, che possono essere resi possibili solo utilizzando l'interfaccia, l'esempio è in injection dependence.

    
risposta data 15.09.2011 - 19:45
fonte
1

Un'implementazione del mondo reale:

Puoi lanciare un oggetto come tipo di interfaccia:

IHelper h = (IHelper)o;
h.HelperMethod();

Puoi creare un elenco di un'interfaccia

List<IHelper> HelperList = new List<IHelper>();

Con questi oggetti è possibile accedere a qualsiasi metodo o proprietà dell'interfaccia. In questo modo è possibile definire un'interfaccia per la parte di un programma. E costruisci la logica attorno ad esso. Quindi qualcun altro può implementare la tua interfaccia nei loro oggetti Business. Se cambiano le BO, possono cambiare la logica dei componenti dell'interfaccia e non richiedere una modifica della logica per il tuo pezzo.

    
risposta data 14.09.2011 - 20:50
fonte
0

Le interfacce offrono una modularità in stile plug-in fornendo un meccanismo che consente alle classi di comprendere (e sottoscrivere) determinati tipi di messaggi forniti dal sistema. Elaborerò.

Nella tua applicazione, decidi che ogni volta che un modulo viene caricato o ricaricato vuoi che tutte le cose che ospita siano cancellate. Definisci un'interfaccia IClear che implementa Clear . Inoltre, si decide che ogni volta che l'utente preme il pulsante di salvataggio, il modulo deve tentare di mantenere il suo stato. Pertanto, tutto ciò che rimane di ISave riceve un messaggio per mantenere il suo stato. Ovviamente, in pratica, la maggior parte delle interfacce gestisce diversi messaggi.

Ciò che distingue le interfacce è che il comportamento comune può essere raggiunto senza ereditarietà. La classe che implementa una determinata interfaccia comprende semplicemente come comportarsi quando viene emesso un comando (un messaggio di comando) o come rispondere quando interrogato (un messaggio di query). In sostanza, le classi nella tua applicazione comprendono i messaggi forniti dall'applicazione. Ciò semplifica la costruzione di un sistema modulare in cui le cose possono essere tappate.

Nella maggior parte delle lingue esistono meccanismi (come LINQ ) per interrogare cose che rispettano un interfaccia. Questo di solito ti aiuterà a eliminare la logica condizionale perché non dovrai dire cose dissimili (che non sono necessarie derivate dalla stessa catena ereditaria) come comportarti in modo simile (secondo un particolare messaggio). Invece, raccogli tutto ciò che comprende un determinato messaggio (si attiene all'interfaccia) e pubblica il messaggio.

Ad esempio, potresti sostituire ...

Me.PublishDate.Clear()
Me.Subject.Clear()
Me.Body.Clear()

... con:

For Each ctl As IClear In Me.Controls.OfType(Of IClear)()
    ctl.Clear()
Next

Che suona molto bene:

Hear ye, Hear ye! Would everyone who understands clearing, please Clear now!

In questo modo, possiamo evitare programmaticamente di dire ogni cosa per chiarire se stessa. E quando in futuro verranno aggiunti elementi cleabili, risponderanno semplicemente senza alcun codice aggiuntivo.

    
risposta data 19.06.2014 - 01:00
fonte
0

Quello che segue è pseudocodice:

class MyClass{

    private MyInterface = new MyInterfaceImplementationB();

    // Code using Thingy 

}

interface MyInterface{

    myMethod();

}

class MyInterfaceImplementationA{ myMethod(){ // method implementation A } }

class MyInterfaceImplementationB{ myMethod(){ // method implementation B } }

class MyInterfaceImplementationC{ myMethod(){ // method implementation C } }

Le ultime classi possono essere implementazioni completamente diverse.

A meno che non sia possibile l'ereditarietà multipla, l'ereditarietà impone l'implementazione della classe genitore rendendo le cose più rigide. La programmazione contro le interfacce, d'altro canto, può consentire al tuo codice o ad un framework di essere estremamente flessibile. Se ti capita di imbattersi in un caso in cui vorresti poter scambiare le classi in una catena ereditaria capirai perché.

Ad esempio, un framework che fornisce un Reader originariamente concepito per leggere i dati dal disco potrebbe essere reimplementato per fare qualcosa della stessa natura ma in un modo completamente diverso. Ad esempio, interpreta il codice Morse.

    
risposta data 11.08.2015 - 10:54
fonte

Leggi altre domande sui tag