Interfacce - Qual è il punto?


269

La ragione delle interfacce mi sfugge davvero. Da quello che ho capito, è una specie di soluzione per la multi-eredità inesistente che non esiste in C # (o almeno così mi è stato detto).

Tutto quello che vedo è che tu predefinisci alcuni membri e funzioni, che poi devono essere ridefiniti nuovamente nella classe. In questo modo l'interfaccia è ridondante. Sembra solo sintattico ... beh, spazzatura per me (per favore, senza offesa significava spazzatura come in cose inutili).

Nell'esempio riportato di seguito, tratto da un thread di interfacce C # diverso su overflow dello stack, creerei semplicemente una classe base chiamata Pizza anziché un'interfaccia.

esempio semplice (tratto da un contributo di overflow dello stack diverso)

public interface IPizza
{
    public void Order();
}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}

Ho la sensazione che ci siano duplicati di questa domanda qui su SO, ma sembrano tutti spiegare semplicemente la parte contrattuale di un'interfaccia, quindi non sono sicuro che si applichino.
Lasse V. Karlsen,

7
cercando di essere un utente gentile e ordinato, tendo a cercare la mia risposta prima in vari forum prima di pubblicare qualcosa. Sfortunatamente, la maggior parte di loro ha iniziato in una fase successiva e il resto non ha aiutato. Ero già alle prese con la base "Perché farlo?" come mi è sembrato inutile complicarlo eccessivamente. Btw. Grazie a tutti per le risposte molto veloci. Devo prima digerirli tutti, ma ora penso di avere una buona idea del punto. Sembra che l'ho sempre guardato da una prospettiva diversa. Grazie mille per il vostro aiuto.
Nebelhom,


1
Inoltre le interfacce aiutano a stabilire l'ereditarietà come per i structtipi.
Ja72,

3
Hmm, l'OP stava chiedendo "Da quello che ho capito, l'interfaccia è una specie di soluzione per l'eredità multipla inesistente che non esiste in C #. (A parte questo, nell'esempio citato nella pizza del libro di testo) Vorrei solo usa una classe base invece di un'interfaccia ". Quindi la maggior parte delle risposte ha fornito un esempio che può essere implementato da una classe di base (astratta) o un esempio per mostrare come l'interfaccia è necessaria per lo scenario multi-ereditarietà. Quelle risposte sono tutte buone, ma non stanno semplicemente ribadendo qualcosa che l'OP già sa? Non c'è da stupirsi che OP abbia finito per scegliere una risposta senza esempi. LOL
RayLuo,

Risposte:


176

Il punto è che l'interfaccia rappresenta un contratto . Una serie di metodi pubblici che ogni classe di implementazione deve avere. Tecnicamente, l'interfaccia governa solo la sintassi, cioè quali metodi ci sono, quali argomenti ottengono e cosa restituiscono. Di solito incapsulano anche la semantica, anche se solo attraverso la documentazione.

È quindi possibile avere diverse implementazioni di un'interfaccia e scambiarle a piacimento. Nel tuo esempio, poiché ogni istanza di pizza è una IPizzapuoi usare IPizzaovunque gestisci un'istanza di un tipo di pizza sconosciuto. Qualsiasi istanza il cui tipo eredita da IPizzaè garantita come ordinabile, in quanto ha un Order()metodo.

Python non è tipizzato staticamente, quindi i tipi vengono mantenuti e controllati durante l'esecuzione. Quindi puoi provare a chiamare un Order()metodo su qualsiasi oggetto. Il tempo di esecuzione è felice fintanto che l'oggetto ha un metodo del genere e probabilmente fa spallucce e dice "Meh". Non così in C #. Il compilatore è responsabile di effettuare le chiamate corrette e se ha solo qualche casuale, objectil compilatore non sa ancora se l'istanza durante il runtime avrà quel metodo. Dal punto di vista del compilatore non è valido poiché non può verificarlo. (Puoi fare queste cose con la riflessione o la dynamicparola chiave, ma adesso va un po 'lontano, immagino.)

Si noti inoltre che un'interfaccia nel solito senso non deve necessariamente essere un C # interface, potrebbe essere anche una classe astratta o addirittura una classe normale (che può tornare utile se tutte le sottoclassi devono condividere un codice comune - nella maggior parte dei casi , tuttavia, è interfacesufficiente).


1
+1, anche se non direi che l'interfaccia (nel senso di un contratto) può essere una classe astratta o normale.
Groo,

3
Vorrei aggiungere che non puoi aspettarti di capire le interfacce in un paio di minuti. Penso che non sia ragionevole comprendere le interfacce se non si hanno anni di esperienza di programmazione orientata agli oggetti. È possibile aggiungere alcuni collegamenti ai libri. Vorrei suggerire: Dependency Injection in .NET, che è davvero quanto profondo arriva il buco rabbioso, non solo una dolce introduzione.
annodare il

2
Ah, c'è il problema che io non ho idea di DI. Ma immagino che il problema principale del richiedente sia il motivo per cui sono necessari quando in Python funziona senza. Questo è ciò che il più grande paragrafo della mia risposta ha cercato di fornire. Non penso che sia necessario scavare in ogni modello e pratica che utilizza le interfacce qui.
Joey,

1
Bene, allora la tua domanda diventa: "Perché usare le lingue tipizzate staticamente quando le lingue tipizzate dinamicamente sono più facili da programmare?". Sebbene non sia l'esperto a rispondere a questa domanda, posso avventurarmi nel dire che le prestazioni sono il problema decisivo. Quando viene effettuata una chiamata a un oggetto Python, il processo deve decidere durante l'esecuzione se quell'oggetto ha un metodo chiamato "Ordine", mentre se si effettua la chiamata a un oggetto C #, è già stabilito che implementa quel metodo e il la chiamata può essere effettuata a tale indirizzo.
Boluc Papuccuoglu,

3
@BolucPapuccuoglu: Oltre a ciò, con un oggetto tipicamente statico se si conoscono gli fooattrezzi IWidget, un programmatore che vede una chiamata foo.woozle()può guardare la documentazione IWidgete sapere cosa dovrebbe fare quel metodo . Il programmatore potrebbe non avere modo di sapere da dove verrà il codice per l'implementazione effettiva, ma qualsiasi tipo che rispetti il IWidgetcontratto di interfaccia verrà implementato fooin modo coerente con quel contratto. In un linguaggio dinamico, al contrario, non ci sarebbe un chiaro punto di riferimento per cosa foo.woozle()dovrebbe significare.
supercat,

440

Nessuno ha davvero spiegato in termini semplici come le interfacce siano utili, quindi ho intenzione di provarlo (e rubare un'idea dalla risposta di Shamim).

Prendiamo l'idea di un servizio di ordinazione della pizza. Puoi avere più tipi di pizze e un'azione comune per ogni pizza è preparare l'ordine nel sistema. Ogni pizza deve essere preparata ma ogni pizza è preparata in modo diverso . Ad esempio, quando viene ordinata una pizza con crosta farcita, il sistema probabilmente deve verificare che alcuni ingredienti siano disponibili al ristorante e mettere da parte quelli che non sono necessari per le pizze per piatti profondi.

Quando scrivi questo codice, tecnicamente potresti semplicemente farlo

public class Pizza()
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;

            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;

            //.... etc.
        }
    }
}

Tuttavia, le pizze per piatti profondi (in termini di C #) possono richiedere l'impostazione di diverse proprietà nel Prepare()metodo rispetto alla crosta farcita, e quindi si finiscono con molte proprietà opzionali e la classe non si ridimensiona bene (e se si aggiungono nuovi tipi di pizza).

Il modo corretto per risolvere questo è utilizzare l'interfaccia. L'interfaccia dichiara che tutte le pizze possono essere preparate, ma ogni pizza può essere preparata in modo diverso. Quindi se hai le seguenti interfacce:

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

Ora il tuo codice di gestione degli ordini non ha bisogno di sapere esattamente quali tipi di pizze sono stati ordinati per gestire gli ingredienti. Ha solo:

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

Anche se ogni tipo di pizza è preparato in modo diverso, questa parte del codice non deve preoccuparsi del tipo di pizza con cui abbiamo a che fare, sa solo che viene chiamata pizza e quindi ogni chiamata a Preparepreparerà automaticamente ogni pizza correttamente in base al suo tipo, anche se la collezione ha più tipi di pizze.


11
+1, mi è piaciuto l'esempio dell'utilizzo di un elenco per mostrare l'uso di un'interfaccia.
danielunderwood,

21
Buona risposta, ma forse modificala per chiarire perché un'interfaccia in questo caso è migliore del semplice utilizzo di una classe astratta (per un esempio così semplice, una classe astratta può davvero essere migliore?)
Jez

3
Questa è la risposta migliore C'è un piccolo errore di battitura o potresti essere solo copiare e incollare il codice. Nell'interfaccia C # non dichiari modificatori di accesso come public. Quindi all'interno dell'interfaccia dovrebbe essere solovoid Prepare();
Dush il

26
Non vedo come questo risponda alla domanda. Questo esempio avrebbe potuto essere fatto altrettanto facilmente con una classe base e un metodo astratto, che è esattamente ciò che la domanda originale ha sottolineato.
Jamie Kitson,

4
Le interfacce non entrano davvero nella loro ragione fino a quando non ne hai molte in giro. Puoi ereditare solo da una singola classe, astratta o no, ma puoi implementare tutte le diverse interfacce in una singola classe che desideri. All'improvviso non hai bisogno di una porta nel telaio di una porta; tutto ciò che è IOpenable farà, che si tratti di una porta, una finestra, una cassetta delle lettere, lo chiami.
mtnielsen,

123

Per me, all'inizio, il punto su questi è diventato chiaro solo quando smetti di guardarli come cose per rendere il tuo codice più facile / veloce da scrivere - questo non è il loro scopo. Hanno una serie di usi:

(Questo perderà l'analogia della pizza, poiché non è molto facile visualizzarne l'uso)

Supponi che stai realizzando un semplice gioco sullo schermo e che avrà creature con cui interagisci.

A: Possono semplificare la manutenzione del codice in futuro introducendo un accoppiamento libero tra il front-end e l'implementazione del back-end.

Potresti scrivere questo per cominciare, poiché ci saranno solo troll:

// This is our back-end implementation of a troll
class Troll
{
    void Walk(int distance)
    {
        //Implementation here
    }
}

Fine frontale:

function SpawnCreature()
{
    Troll aTroll = new Troll();

    aTroll.Walk(1);
}

Due settimane dopo, il marketing decide che hai bisogno anche degli Orchi, mentre leggono su di loro su Twitter, quindi dovresti fare qualcosa del tipo:

class Orc
{
    void Walk(int distance)
    {
        //Implementation (orcs are faster than trolls)
    }
}

Fine frontale:

void SpawnCreature(creatureType)
{
    switch(creatureType)
    {
         case Orc:

           Orc anOrc = new Orc();
           anORc.Walk();

          case Troll:

            Troll aTroll = new Troll();
             aTroll.Walk();
    }
}

E puoi vedere come questo inizia a diventare disordinato. Puoi usare un'interfaccia qui in modo che il tuo front-end venga scritto una volta e (qui il bit importante) testato, e puoi quindi collegare ulteriori elementi di back-end come richiesto:

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

Il front-end è quindi:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

Il front-end ora si preoccupa solo dell'interfaccia ICreature - non si preoccupa dell'implementazione interna di un troll o di un orco, ma solo del fatto che implementano ICreature.

Un punto importante da notare quando si guarda questo da questo punto di vista è che avresti potuto facilmente usare una classe di creatura astratta, e da questo punto di vista, questo ha lo stesso effetto.

E potresti estrarre la creazione in una fabbrica:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

E il nostro front-end diventerebbe quindi:

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

Il front-end ora non deve nemmeno avere un riferimento alla libreria in cui sono implementati Troll e Orc (a condizione che la fabbrica sia in una libreria separata) - non ha bisogno di sapere nulla di loro.

B: Supponi di avere funzionalità che solo alcune creature avranno nella tua struttura di dati altrimenti omogenea , ad es

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

Il front-end potrebbe quindi essere:

void SpawnCreatureInSunlight(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();

    if (creature is ICanTurnToStone)
    {
       (ICanTurnToStone)creature.TurnToStone();
    }
}

C: Utilizzo per iniezione di dipendenza

La maggior parte dei framework di iniezione delle dipendenze è più facile da lavorare quando esiste un accoppiamento molto lento tra il codice front-end e l'implementazione del back-end. Se prendiamo il nostro esempio di fabbrica sopra e la nostra fabbrica implementa un'interfaccia:

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

Il nostro front-end potrebbe quindi iniettare questo (ad esempio un controller API MVC) attraverso il costruttore (in genere):

public class CreatureController : Controller {

   private readonly ICreatureFactory _factory;

   public CreatureController(ICreatureFactory factory) {
     _factory = factory;
   }

   public HttpResponseMessage TurnToStone(string creatureType) {

       ICreature creature = _factory.GetCreature(creatureType);

       creature.TurnToStone();

       return Request.CreateResponse(HttpStatusCode.OK);
   }
}

Con il nostro framework DI (ad esempio Ninject o Autofac), possiamo configurarli in modo che in fase di esecuzione venga creata un'istanza di CreatureFactory ogni volta che è necessario un ICreatureFactory in un costruttore - questo rende il nostro codice piacevole e semplice.

Significa anche che quando scriviamo un test unitario per il nostro controller, possiamo fornire un ICreatureFactory beffardo (ad esempio se l'implementazione concreta richiede l'accesso al DB, non vogliamo che i nostri test unitari dipendano da quello) e testare facilmente il codice nel nostro controller .

D: Esistono altri usi, ad esempio hai due progetti A e B che per motivi "legacy" non sono ben strutturati e A ha un riferimento a B.

Quindi trovi la funzionalità in B che deve chiamare un metodo già in A. Non puoi farlo usando implementazioni concrete quando ottieni un riferimento circolare.

È possibile avere un'interfaccia dichiarata in B che la classe in A quindi implementa. Il tuo metodo in B può essere passato a un'istanza di una classe che implementa l'interfaccia senza problemi, anche se l'oggetto concreto è di un tipo in A.


6
Non è fastidioso quando trovi una risposta che stava cercando di dire quello che eri, ma lo fa molto meglio - stackoverflow.com/a/93998/117215
Paddy,

18
La buona notizia ora è una pagina morta e la tua no :). Buon esempio!
Sealer_05

1
La tua discussione in C mi ha perso un po '. Ma mi piacciono le tue discussioni A e B perché entrambe spiegano essenzialmente come le interfacce possono essere utilizzate per fornire tipi comuni di funzionalità tra più classi. L'area delle interfacce che è ancora confusa per me è il modo in cui le interfacce vengono utilizzate per un accoppiamento più lento. Forse è a questo che si è rivolta la tua discussione in C? In tal caso, suppongo di aver bisogno di un esempio più dettagliato :)
user2075599

1
Mi sembra che nel tuo esempio ICreature sia più adatto ad essere una classe base astratta (Creatura?) Per Troll e Orco. Quindi qualsiasi logica comune tra le creature potrebbe essere implementata lì. Ciò significa che non hai assolutamente bisogno dell'interfaccia ICreature ...
Skarsnik,

1
@Skarsnik - piuttosto, questo è ciò che riguarda questa nota: "Un punto importante da notare quando si guarda questo da questo punto di vista è che potresti anche aver facilmente usato una classe di creature astratte, e da questo punto di vista, questo ha lo stesso effetto."
Paddy,

33

Ecco i tuoi esempi spiegati:

public interface IFood // not Pizza
{
    public void Prepare();

}

public class Pizza : IFood
{
    public void Prepare() // Not order for explanations sake
    {
        //Prepare Pizza
    }
}

public class Burger : IFood
{
    public void Prepare()
    {
        //Prepare Burger
    }
}

27

Gli esempi sopra non hanno molto senso. Puoi realizzare tutti gli esempi sopra usando le classi (classe astratta se vuoi che si comporti solo come un contratto ):

public abstract class Food {
    public abstract void Prepare();
}

public class Pizza : Food  {
    public override void Prepare() { /* Prepare pizza */ }
}

public class Burger : Food  {
    public override void Prepare() { /* Prepare Burger */ }
}

Ottieni lo stesso comportamento dell'interfaccia. Puoi creare List<Food>e ripetere ciò senza sapere quale classe si trova in cima.

Un esempio più adeguato sarebbe l'ereditarietà multipla:

public abstract class MenuItem {
    public string Name { get; set; }
    public abstract void BringToTable();
}

// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
    public override void BringToTable() { /* Bring soda to table */ }
}


// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
    void Cook();
}

public class Pizza : MenuItem, IFood {
    public override void BringToTable() { /* Bring pizza to table */ }
    public void Cook() { /* Cook Pizza */ }
}

public class Burger : MenuItem, IFood {
    public override void BringToTable() { /* Bring burger to table */ }
    public void Cook() { /* Cook Burger */ }
}

Quindi puoi usarli tutti come MenuIteme non preoccuparti di come gestiscono ogni chiamata di metodo.

public class Waiter {
    public void TakeOrder(IEnumerable<MenuItem> order) 
    {
        // Cook first
        // (all except soda because soda is not IFood)
        foreach (var food in order.OfType<IFood>())
            food.Cook();

        // Bring them all to the table
        // (everything, including soda, pizza and burger because they're all menu items)
        foreach (var menuItem in order)
            menuItem.BringToTable();
    }
}

2
Ho dovuto scorrere fino in fondo qui per trovare una risposta che risponda effettivamente alla domanda "perché non usare semplicemente una classe astratta anziché un'interfaccia?". Sembra davvero che gestisca solo la mancanza di ereditarietà multipla di c #.
Sintassi Regola

2
Sì, questa è la migliore spiegazione per me. L'unico che spiega chiaramente perché l'interfaccia può essere più utile di una classe astratta. (vale a dire: una pizza è un MenuItem ED è anche cibo mentre con una classe astratta può essere solo l'una o l'altra ma non entrambe)
Maxter

25

Spiegazione semplice con analogia

Il problema da risolvere: qual è lo scopo del polimorfismo?

Analogia: quindi sono un precursore in un cantiere edile.

I commercianti camminano continuamente sul cantiere. Non so chi varcherà quelle porte. Ma in pratica dico loro cosa fare.

  1. Se è un falegname, dico: costruisci impalcature di legno.
  2. Se è un idraulico, dico: "Installa i tubi"
  3. Se è un elettricista, dico "Estrai i cavi e sostituiscili con quelli in fibra ottica".

Il problema con l'approccio sopra è che devo: (i) sapere chi sta camminando in quella porta e, a seconda di chi è, devo dire loro cosa fare. Ciò significa che devo sapere tutto su un particolare commercio. Ci sono costi / benefici associati a questo approccio:

Le implicazioni del sapere cosa fare:

  • Ciò significa che se il codice del carpentiere cambia da: BuildScaffolding()a BuildScaffold()(cioè un leggero cambio di nome), dovrò anche cambiare la classe chiamante (cioè la Forepersonclasse) - dovrete fare due modifiche al codice invece di (sostanzialmente ) solo uno. Con il polimorfismo (in pratica) devi solo fare una modifica per ottenere lo stesso risultato.

  • In secondo luogo non dovrai chiedere costantemente: chi sei? ok fai questo ... chi sei? ok fallo ..... polimorfismo - ASCIUGA quel codice ed è molto efficace in determinate situazioni:

  • con il polimorfismo puoi facilmente aggiungere ulteriori classi di commercianti senza cambiare alcun codice esistente. (ovvero il secondo dei principi di progettazione SOLID: principio Open-close).

La soluzione

Immagina uno scenario in cui, indipendentemente da chi entra dalla porta, posso dire: "Lavoro ()" e fanno i loro lavori di rispetto in cui sono specializzati: l'idraulico avrebbe a che fare con i tubi e l'elettricista avrebbe a che fare con i cavi.

Il vantaggio di questo approccio è che: (i) non ho bisogno di sapere esattamente chi sta varcando quella porta - tutto quello che devo sapere è che saranno un tipo di tradie e che potranno fare un lavoro, e in secondo luogo , (ii) non ho bisogno di sapere nulla su quel particolare commercio. Il commerciante se ne occuperà.

Quindi invece di questo:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

Posso fare qualcosa del genere:

ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.

tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!

Qual è il vantaggio?

Il vantaggio è che se i requisiti specifici di lavoro del carpentiere ecc. Cambiano, allora il caposquadra non dovrà cambiare il suo codice - non ha bisogno di sapere o preoccuparsene. Tutto ciò che conta è che il falegname sappia cosa si intende per Work (). In secondo luogo, se un nuovo tipo di operaio edile arriva sul posto di lavoro, allora il caposquadra non ha bisogno di sapere nulla del commercio - tutto il caposquadra si preoccupa se il muratore (.eg Welder, Glazier, Tiler ecc.) Può esegui un po 'di lavoro ().


Problema e soluzione illustrati (con e senza interfacce):

Nessuna interfaccia (esempio 1):

Esempio 1: senza interfaccia

Nessuna interfaccia (esempio 2):

Esempio 2: senza interfaccia

Con un'interfaccia:

Esempio 3: i vantaggi dell'utilizzo di un'interfaccia

Sommario

Un'interfaccia ti consente di convincere la persona a svolgere il lavoro a cui è assegnata, senza che tu abbia la conoscenza di esattamente chi sono o le specifiche di ciò che possono fare. Ciò ti consente di aggiungere facilmente nuovi tipi (di scambio) senza cambiare il tuo codice esistente (bene tecnicamente lo cambi un pochino) e questo è il vero vantaggio di un approccio OOP rispetto a una metodologia di programmazione più funzionale.

Se non capisci quanto sopra o se non è chiaro, chiedi in un commento e cercherò di migliorare la risposta.


4
Più uno per il ragazzo che arriva a lavorare in una felpa con cappuccio.
Yusha,

11

In assenza della digitazione di anatre, poiché è possibile utilizzarla in Python, C # si basa su interfacce per fornire astrazioni. Se le dipendenze di una classe fossero tutti tipi concreti, non potresti passare in nessun altro tipo - usando le interfacce puoi passare in qualsiasi tipo che implementa l'interfaccia.


3
+1 Esatto, se hai la digitazione anatra, non hai bisogno di interfacce. Ma applicano una sicurezza di tipo più forte.
Groo,

11

L'esempio della pizza è negativo perché dovresti usare una classe astratta che gestisce l'ordinamento e le pizze dovrebbero semplicemente ignorare il tipo di pizza, ad esempio.

Usi le interfacce quando hai una proprietà condivisa, ma le tue classi ereditano da luoghi diversi o quando non hai un codice comune che potresti usare. Ad esempio, vengono utilizzate cose che possono essere eliminateIDisposable , sai che verranno eliminate, semplicemente non sai cosa accadrà quando verrà eliminato.

Un'interfaccia è solo un contratto che ti dice alcune cose che un oggetto può fare, quali parametri e quali tipi di ritorno aspettarsi.


10

Considera il caso in cui non controlli o possiedi le classi di base.

Prendi ad esempio i controlli visivi, in .NET per Winforms tutti ereditano dalla classe base Control, che è completamente definita nel framework .NET.

Supponiamo che tu sia nel business della creazione di controlli personalizzati. Desideri creare nuovi pulsanti, caselle di testo, visualizzazioni elenco, griglie, quant'altro e desideri che tutti abbiano determinate funzionalità esclusive per il tuo set di controlli.

Ad esempio, potresti desiderare un modo comune per gestire i temi o un modo comune per gestire la localizzazione.

In questo caso non puoi "semplicemente creare una classe base" perché se lo fai, devi reimplementare tutto ciò che riguarda i controlli.

Scenderai invece da Button, TextBox, ListView, GridView, ecc. E aggiungerai il tuo codice.

Ma questo pone un problema, come puoi ora identificare quali controlli sono "tuoi", come puoi creare un codice che dice "per tutti i controlli sul modulo che sono miei, imposta il tema su X".

Inserisci le interfacce.

Le interfacce sono un modo per guardare un oggetto, per determinare che l'oggetto aderisce a un determinato contratto.

Dovresti creare "YourButton", scendere da Button e aggiungere il supporto per tutte le interfacce di cui hai bisogno.

Ciò ti consentirebbe di scrivere codice come il seguente:

foreach (Control ctrl in Controls)
{
    if (ctrl is IMyThemableControl)
        ((IMyThemableControl)ctrl).SetTheme(newTheme);
}

Ciò non sarebbe possibile senza interfacce, invece dovresti scrivere codice in questo modo:

foreach (Control ctrl in Controls)
{
    if (ctrl is MyThemableButton)
        ((MyThemableButton)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableTextBox)
        ((MyThemableTextBox)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableGridView)
        ((MyThemableGridView)ctrl).SetTheme(newTheme);
    else ....
}

Sì, lo so, non dovresti usare "is" e poi il cast, lascialo da parte.
Lasse V. Karlsen,

4
sospiro lo so, ma questo è fortuito qui.
Lasse V. Karlsen,

7

In questo caso, potresti (e probabilmente vorresti) definire una classe di base Pizza ed ereditarla. Tuttavia, ci sono due motivi per cui le interfacce ti consentono di fare cose che non possono essere raggiunte in altri modi:

  1. Una classe può implementare più interfacce. Definisce solo le funzionalità che la classe deve avere. L'implementazione di una gamma di interfacce significa che una classe può svolgere più funzioni in luoghi diversi.

  2. Un'interfaccia può essere definita in un ambito più ampio rispetto alla classe o al chiamante. Ciò significa che è possibile separare la funzionalità, separare la dipendenza del progetto e mantenere la funzionalità in un progetto o classe e l'implementazione di questo altrove.

Una delle implicazioni di 2 è che puoi cambiare la classe che stai usando, richiedendo solo che implementi l'interfaccia appropriata.


6

Considera che non puoi usare l'ereditarietà multipla in C #, quindi rivedi la tua domanda.



4

Se sto lavorando su un'API per disegnare forme, potrei voler usare DirectX o chiamate grafiche o OpenGL. Quindi, creerò un'interfaccia, che astrarrà la mia implementazione da ciò che chiami.

Così si chiama un metodo di fabbrica: MyInterface i = MyGraphics.getInstance(). Quindi, hai un contratto, quindi sai in quali funzioni puoi aspettarti MyInterface. Quindi, puoi chiamare i.drawRectangleo i.drawCubee sapere che se si scambia una libreria con un'altra, le funzioni sono supportate.

Ciò diventa più importante se si utilizza Iniezione delle dipendenze, poiché in un file XML è possibile scambiare le implementazioni.

Quindi, potresti avere una libreria di crittografia che può essere esportata per uso generale e un'altra che è in vendita solo a società americane, e la differenza è che si modifica un file di configurazione e il resto del programma non lo è cambiato.

Questo è usato molto con le raccolte in .NET, poiché dovresti usare, ad esempio, Listvariabili e non preoccuparti se si trattava di una ArrayList o di LinkedList.

Finché si codifica l'interfaccia, lo sviluppatore può modificare l'implementazione effettiva e il resto del programma rimane invariato.

Questo è utile anche durante il test di unità, poiché puoi deridere intere interfacce, quindi non devo andare a un database, ma a un'implementazione derisa che restituisce solo dati statici, quindi posso testare il mio metodo senza preoccuparmi se il database è inattivo per manutenzione o no.


4

Un'interfaccia è davvero un contratto che le classi di implementazione devono seguire, in realtà è la base per praticamente ogni modello di progettazione che conosco.

Nel tuo esempio, l'interfaccia viene creata perché allora tutto ciò che IS A Pizza, che significa implementa l'interfaccia Pizza, è garantito

public void Order();

Dopo il tuo codice citato potresti avere qualcosa del genere:

public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
      myPizza.order();
}

In questo modo stai usando il polimorfismo e tutto ciò che ti interessa è che i tuoi oggetti rispondano all'ordine ().


4

Ho cercato la parola "composizione" in questa pagina e non l'ho vista una volta. Questa risposta si aggiunge alle risposte di cui sopra.

Uno dei motivi assolutamente cruciali per l'utilizzo delle interfacce in un progetto orientato agli oggetti è che consentono di favorire la composizione rispetto all'eredità. Implementando le interfacce è possibile disaccoppiare le implementazioni dai vari algoritmi che si stanno applicando ad esse.

Questo superbo tutorial "Decorator Pattern" di Derek Banas (che - stranamente - usa anche la pizza come esempio) è un esempio utile:

https://www.youtube.com/watch?v=j40kRwSm4VE


2
Sono davvero scioccato dal fatto che questa non sia la risposta migliore. Le interfacce in tutta la loro utilità, sono di grande utilità nella composizione.
Maciej Sitko,

3

Le interfacce servono per applicare la connessione tra classi diverse. per esempio, hai una classe per auto e un albero;

public class Car { ... }

public class Tree { ... }

vuoi aggiungere una funzionalità masterizzabile per entrambe le classi. Ma ogni classe ha i propri modi per bruciare. quindi fai semplicemente;

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}

2
La domanda che mi perseguita in esempi come questo è: perché questo aiuta? Posso ora passare l'argomento di tipo IBurnable a un metodo e tutte le classi con l'interfaccia IBurnable possono essere gestite? Come nell'esempio di Pizza che ho trovato. È bello che tu possa farlo, ma non ne vedo il beneficio, in quanto tale. Potresti estendere l'esempio (perché al momento sono davvero spessa) o fare un esempio che non funzionerebbe con esso (di nuovo, perché al momento mi sento davvero spessa). Molte grazie.
Nebelhom,

1
Essere d'accordo. Interface = "Can do". Class / Abstract Calss = "Is A"
Peter.Wang

3

Avrai delle interfacce, quando ne avrai bisogno :) Puoi studiare esempi, ma hai bisogno di Aha! effetto per ottenerli davvero.

Ora che sai quali sono le interfacce, basta codificare senza di esse. Prima o poi incontrerai un problema, in cui l'uso delle interfacce sarà la cosa più naturale da fare.


3

Sono sorpreso che non molti post contengano il motivo più importante per un'interfaccia: i design pattern . È il quadro più ampio sull'uso dei contratti, e sebbene sia una decorazione di sintassi per il codice macchina (ad essere onesti, il compilatore probabilmente li ignora semplicemente), l'astrazione e le interfacce sono fondamentali per OOP, comprensione umana e architetture di sistema complesse.

Espandiamo l'analogia della pizza per dire un pasto completo di 3 portate. Avremo ancora l' Prepare()interfaccia di base per tutte le nostre categorie di alimenti, ma avremmo anche dichiarazioni astratte per le selezioni dei corsi (antipasto, principale, dessert) e proprietà diverse per i tipi di cibo (salato / dolce, vegetariano / non vegetariano, senza glutine ecc.).

Sulla base di queste specifiche, abbiamo potuto implementare il modello Abstract Factory per concettualizzare l'intero processo, ma utilizzare le interfacce per garantire che solo le basi fossero concrete. Tutto il resto potrebbe diventare flessibile o incoraggiare il polimorfismo, mantenendo tuttavia l'incapsulamento tra le diverse classi di Coursetale ICourseinterfaccia.

Se avessi più tempo, vorrei fare un esempio completo di questo, o qualcuno può estenderlo per me, ma in sintesi, un'interfaccia C # sarebbe lo strumento migliore nella progettazione di questo tipo di sistema.


1
Questa risposta merita più punti! Le interfacce si illuminano quando vengono utilizzate in modelli progettati, ad es. The State Pattern. Vedi plus.google.com/+ZoranHorvat-Programmazione per ulteriori informazioni
Alex Nolasco

3

Ecco un'interfaccia per oggetti che hanno una forma rettangolare:

interface IRectangular
{
    Int32 Width();
    Int32 Height();
}

Tutto ciò che richiede è implementare modi per accedere alla larghezza e all'altezza dell'oggetto.

Ora definiamo un metodo che funzionerà su qualsiasi oggetto che sia IRectangular:

static class Utils
{
    public static Int32 Area(IRectangular rect)
    {
        return rect.Width() * rect.Height();
    }
}

Ciò restituirà l'area di qualsiasi oggetto rettangolare.

Implementiamo una classe SwimmingPoolrettangolare:

class SwimmingPool : IRectangular
{
    int width;
    int height;

    public SwimmingPool(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

E un'altra classe Houseche è anche rettangolare:

class House : IRectangular
{
    int width;
    int height;

    public House(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

Detto questo, puoi chiamare il Areametodo su case o piscine:

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));

In questo modo, le tue classi possono "ereditare" il comportamento (metodi statici) da qualsiasi numero di interfacce.


3

Un'interfaccia definisce un contratto tra il fornitore di una determinata funzionalità e i consumatori corrispondenti. Disaccoppia l'implementazione dal contratto (interfaccia). Dovresti dare un'occhiata all'architettura e al design orientati agli oggetti. Potresti voler iniziare con wikipedia: http://en.wikipedia.org/wiki/Interface_(computing)


3

Che cosa ?

Le interfacce sono fondamentalmente un contratto che tutte le classi che implementano l'interfaccia dovrebbero seguire. Sembrano una classe ma non hanno implementazione.

In C#Nomi di interfaccia per convenzione è definito prefissando un 'I', quindi se si desidera avere un'interfaccia chiamata forme, la si dichiarerebbe comeIShapes

Ora perché ?

Improves code re-usability

Diciamo che vuoi disegnare Circle, Triangle. puoi raggrupparli insieme e chiamarli Shapese avere metodi per disegnare Circlee Triangle Ma avere un'implementazione concreta sarebbe una cattiva idea perché domani potresti decidere di averne altri 2 Shapes Rectangle& Square. Ora quando li aggiungi c'è una grande possibilità che potresti rompere altre parti del tuo codice.

Con Interface si isola la diversa implementazione dal contratto


Scenario dal vivo Giorno 1

Ti è stato chiesto di creare un'app per disegnare Circle e Triangle

interface IShapes
{
   void DrawShape();
   
 }

class Circle : IShapes
{
    
    public void DrawShape()
    {
        Console.WriteLine("Implementation to Draw a Circle");
    }
}

Class Triangle: IShapes
{
     public void DrawShape()
    {
        Console.WriteLine("Implementation to draw a Triangle");
    }
}
static void Main()
{
     List <IShapes> shapes = new List<IShapes>();
        shapes.Add(new Circle());
        shapes.Add(new Triangle());

        foreach(var shape in shapes)
        {
            shape.DrawShape();
        }
}

Scenario dal vivo Giorno 2

Se ti è stato chiesto di aggiungere Squaree Rectanglead esso, tutto ciò che devi fare è creare l'impianto per esso in class Square: IShapese in MainAggiungi alla listashapes.Add(new Square());


Perché stai aggiungendo una risposta a una domanda di 6 anni con dozzine di altre risposte votate centinaia di volte? Non c'è nulla qui che non sia già stato detto.
Jonathon Reinhart,

@JonathonReinhart, sì, l'ho pensato, ma poi stavo pensando a questo esempio e il modo in cui è stato spiegato aiuterà a relazionarsi con un corpo meglio degli altri.
Clint

2

Ci sono molte buone risposte qui, ma vorrei provare da una prospettiva leggermente diversa.

Potresti avere familiarità con i principi SOLID della progettazione orientata agli oggetti. In sintesi:

S - Principio di responsabilità singola O - Principio di apertura / chiusura L - Principio di sostituzione di Liskov I - Principio di segregazione dell'interfaccia D - Principio di inversione di dipendenza

Seguire i principi SOLIDI aiuta a produrre un codice pulito, ben ponderato, coerente e liberamente accoppiato. Dato che:

"La gestione delle dipendenze è la sfida chiave nel software su ogni scala" (Donald Knuth)

allora tutto ciò che aiuta nella gestione delle dipendenze è una grande vittoria. Le interfacce e il principio di inversione di dipendenza aiutano davvero a separare il codice dalle dipendenze da classi concrete, quindi il codice può essere scritto e ragionato in termini di comportamenti piuttosto che di implementazioni. Questo aiuta a suddividere il codice in componenti che possono essere composti in fase di esecuzione piuttosto che in fase di compilazione e significa anche che tali componenti possono essere facilmente collegati e scollegati senza dover modificare il resto del codice.

Le interfacce aiutano in particolare con il principio di inversione di dipendenza, in cui il codice può essere suddiviso in una raccolta di servizi, con ogni servizio descritto da un'interfaccia. I servizi possono quindi essere "iniettati" in classi in fase di esecuzione passandoli come parametro del costruttore. Questa tecnica diventa davvero fondamentale se inizi a scrivere unit test e usi lo sviluppo test driven. Provalo! Capirai rapidamente come le interfacce aiutano a suddividere il codice in blocchi gestibili che possono essere testati singolarmente in modo isolato.


1

Lo scopo principale delle interfacce è che stipula un contratto tra te e qualsiasi altra classe che implementa quell'interfaccia che rende il codice disaccoppiato e consente l'espandibilità.


1

Ci sono esempi davvero fantastici.

Un altro, nel caso di un'istruzione switch, non è più necessario mantenere e cambiare ogni volta che si desidera eseguire un'attività in un modo specifico.

Nel tuo esempio di pizza, se vuoi fare una pizza, l'interfaccia è tutto ciò di cui hai bisogno, da lì ogni pizza si prende cura della propria logica.

Questo aiuta a ridurre l'accoppiamento e la complessità ciclomatica. Devi ancora implementare la logica, ma ci sarà meno da tenere traccia nel quadro più ampio.

Per ogni pizza puoi quindi tenere traccia delle informazioni specifiche per quella pizza. Ciò che altre pizze hanno non importa perché solo le altre pizze devono sapere.


1

Il modo più semplice di pensare alle interfacce è riconoscere il significato dell'ereditarietà. Se la classe CC eredita la classe C, significa che:

  1. La classe CC può utilizzare qualsiasi membro pubblico o protetto della classe C come se fosse il proprio, e quindi deve solo implementare cose che non esistono nella classe genitore.
  2. Un riferimento a un CC può essere passato o assegnato a una routine o variabile che prevede un riferimento a un C.

Queste due funzioni dell'eredità sono in un certo senso indipendenti; sebbene l'ereditarietà si applichi entrambi contemporaneamente, è anche possibile applicare il secondo senza il primo. Ciò è utile perché consentire a un oggetto di ereditare membri da due o più classi non correlate è molto più complicato che consentire a un tipo di cosa di essere sostituibile con più tipi.

Un'interfaccia è un po 'come una classe base astratta, ma con una differenza chiave: un oggetto che eredita una classe base non può ereditare nessun'altra classe. Al contrario, un oggetto può implementare un'interfaccia senza influire sulla sua capacità di ereditare qualsiasi classe desiderata o implementare altre interfacce.

Una caratteristica interessante di questo (sottoutilizzato nel framework .net, IMHO) è che consentono di indicare in modo dichiarativo le cose che un oggetto può fare. Alcuni oggetti, ad esempio, vorranno un oggetto di origine dati da cui possano recuperare le cose per indice (come è possibile con un elenco), ma non dovranno archiviare nulla lì. Altre routine avranno bisogno di un oggetto depositario di dati in cui possano archiviare oggetti non per indice (come con Collection.Add), ma non dovranno rileggere nulla. Alcuni tipi di dati consentiranno l'accesso per indice, ma non consentiranno la scrittura; altri consentiranno la scrittura, ma non consentiranno l'accesso per indice. Alcuni, ovviamente, consentiranno entrambi.

Se ReadableByIndex e Appendable fossero classi di base non correlate, sarebbe impossibile definire un tipo che potrebbe essere passato sia a cose che si aspettano un ReadableByIndex sia a cose che si aspettano un'appendibile. Si potrebbe tentare di mitigare ciò facendo sì che ReadableByIndex o Appendable derivino dall'altro; la classe derivata dovrebbe rendere disponibili membri pubblici per entrambi gli scopi, ma avvisa che alcuni membri pubblici potrebbero effettivamente non funzionare. Alcune classi e interfacce di Microsoft lo fanno, ma è piuttosto complicato. Un approccio più pulito consiste nell'avere interfacce per i diversi scopi e quindi gli oggetti implementano interfacce per le cose che possono effettivamente fare. Se uno avesse un'interfaccia IReadableByIndex e un'altra interfaccia IAppendable,


1

Le interfacce possono anche essere collegate in serie per creare un'altra interfaccia. Questa capacità di implementare più interfacce offre allo sviluppatore il vantaggio di aggiungere funzionalità alle proprie classi senza dover modificare la funzionalità di classe corrente (Principi SOLID)

O = "Le classi devono essere aperte per l'estensione ma chiuse per modifica"


1

Per me un vantaggio / vantaggio di un'interfaccia è che è più flessibile di una classe astratta. Poiché è possibile ereditare solo 1 classe astratta ma è possibile implementare più interfacce, le modifiche a un sistema che eredita una classe astratta in molti punti diventa problematica. Se viene ereditato in 100 posizioni, una modifica richiede modifiche a tutte e 100. Tuttavia, con l'interfaccia, è possibile posizionare la nuova modifica in una nuova interfaccia e utilizzare tale interfaccia laddove necessario (Interface Seq. Da SOLID). Inoltre, l'utilizzo della memoria sembra che sarebbe meno con l'interfaccia poiché un oggetto nell'esempio di interfaccia viene utilizzato solo una volta in memoria, nonostante il numero di posizioni che implementano l'interfaccia.


1

Le interfacce sono utilizzate per favorire la coerenza, in un modo che è vagamente accoppiato, il che la rende diversa dalla classe astratta che è strettamente accoppiata. Ecco perché è anche comunemente definito un contratto. Qualunque classe che implementa l'interfaccia si attiene a "regole / sintassi" definito dall'interfaccia e non contiene elementi concreti al suo interno.

Ti faccio solo un esempio supportato dal grafico qui sotto.

Immagina in una fabbrica che ci siano 3 tipi di macchine: una macchina rettangolo, una macchina a triangolo e una macchina poligonale, i tempi sono competitivi e vuoi semplificare la formazione degli operatori, vuoi solo addestrarli in una metodologia di avvio e arresto delle macchine in modo da hanno un pulsante di avvio verde e un pulsante di arresto rosso. Quindi ora su 3 macchine diverse hai un modo coerente di avviare e arrestare 3 diversi tipi di macchine. Ora immagina che queste macchine siano classi e che le classi debbano avere metodi di avvio e arresto, come guiderà la coerenza tra queste classi che può essere molto diversa? L'interfaccia è la risposta.

inserisci qui la descrizione dell'immagine

Un semplice esempio per aiutarti a visualizzare, ci si potrebbe chiedere perché non usare la classe astratta? Con un'interfaccia gli oggetti non devono essere direttamente correlati o ereditati e puoi comunque guidare la coerenza tra classi diverse.

public interface IMachine
{
    bool Start();
    bool Stop();
}

public class Car : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Car started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Car stopped");
        return false;
    }
}

public class Tank : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Tank started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Tank stopped");
        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var car = new Car();
        car.Start();
        car.Stop();

        var tank = new Tank();
        tank.Start();
        tank.Stop();

    }
}

1
class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

Vorrei descriverlo da una prospettiva diversa. Creiamo una storia secondo l'esempio che ho mostrato sopra;

Program, Machine e IMachine sono gli attori della nostra storia. Il programma vuole essere eseguito ma non ha questa capacità e Machine sa come eseguire. Machine e IMachine sono i migliori amici, ma il programma non è in linea con la macchina. Quindi Program e IMachine hanno stretto un accordo e hanno deciso che IMachine dirà a Program come funzionare guardando Machine (come un riflettore).

E il programma impara come eseguire con l'aiuto di IMachine.

Interface fornisce comunicazione e sviluppo di progetti debolmente accoppiati.

PS: ho il metodo della classe concreta come privato. Il mio obiettivo qui è quello di ottenere un accoppiamento libero impedendo l'accesso alle proprietà e ai metodi della classe concreta, lasciando solo il modo di raggiungerli tramite interfacce. (Quindi ho definito esplicitamente i metodi delle interfacce).


1

So di essere molto in ritardo .. (quasi nove anni), ma se qualcuno vuole una piccola spiegazione, puoi farlo:

In parole semplici, usi Interfaccia quando sai cosa può fare un oggetto o quale funzione implementeremo su un oggetto. Esempio Inserisci, Aggiorna ed Elimina.

interface ICRUD{
      void InsertData(); // will insert data
      void UpdateData(); // will update data
      void DeleteData(); // will delete data
}

Nota importante: le interfacce sono SEMPRE pubbliche.

Spero che questo ti aiuti.

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.