Domanda

Qualcuno potrebbe demistificare le interfacce per me o indicarmi alcuni buoni esempi? Continuo a vedere popup di interfacce qua e là, ma non sono mai stato veramente esposto a buone spiegazioni delle interfacce o quando usarle.

Sto parlando di interfacce in un contesto di interfacce rispetto a classi astratte.

È stato utile?

Soluzione

Le interfacce ti permettono di programmare contro un " descrizione " invece di un tipo, che ti consente di associare più liberamente elementi del tuo software.

Pensaci in questo modo: vuoi condividere i dati con qualcuno nel cubo accanto a te, quindi estrai il tuo flash stick e copia / incolla. Cammini nella porta accanto e il ragazzo dice & Quot; è quella USB? & Quot; e tu dici di sì - tutto pronto. Non importa la dimensione della chiavetta, né il produttore - tutto ciò che conta è che sia USB.

Allo stesso modo, le interfacce ti consentono di generare il tuo sviluppo. Usando un'altra analogia: immagina di voler creare un'applicazione che dipingesse virtualmente le auto. Potresti avere una firma come questa:

public void Paint(Car car, System.Drawing.Color color)...

Questo funzionerebbe fino a quando il tuo cliente non avrà detto " ora voglio dipingere camion " così puoi farlo:

public void Paint (Vehicle vehicle, System.Drawing.Color color)...

questo amplierebbe la tua app ... fino a quando il tuo cliente non dicesse " ora voglio dipingere case! " Quello che avresti potuto fare fin dall'inizio è stata creata un'interfaccia:

public interface IPaintable{
   void Paint(System.Drawing.Color color);
}

... e lo ha passato alla tua routine:

public void Paint(IPaintable item, System.Drawing.Color color){
   item.Paint(color);
}

Speriamo che abbia un senso: è una spiegazione piuttosto semplicistica, ma spero ne arrivi al cuore.

Altri suggerimenti

Le interfacce stabiliscono un contratto tra una classe e il codice che la chiama. Consentono inoltre di avere classi simili che implementano la stessa interfaccia ma eseguono azioni o eventi diversi e non devono sapere con chi si sta effettivamente lavorando. Questo potrebbe avere più senso come esempio, quindi lasciami provare qui.

Supponi di avere un paio di classi chiamate Cane, Gatto e Topo. Ognuna di queste classi è un animale domestico e in teoria potresti ereditarle tutte da un'altra classe chiamata animale domestico, ma ecco il problema. Gli animali domestici di per sé non fanno nulla. Non puoi andare al negozio e comprare un animale domestico. Puoi andare a comprare un cane o un gatto, ma un animale domestico è un concetto astratto e non concreto.

Quindi sai che gli animali domestici possono fare certe cose. Possono dormire o mangiare, ecc. Quindi definisci un'interfaccia chiamata IPet e sembra qualcosa del genere (sintassi C #)

public interface IPet
{
    void Eat(object food);
    void Sleep(int duration);
}

Ciascuna delle tue classi Dog, Cat e Mouse implementa IPet.

public class Dog : IPet

Quindi ora ciascuna di queste classi deve avere la propria implementazione di Eat and Sleep. Yay hai un contratto ... Ora che senso ha.

Quindi supponiamo che tu voglia creare un nuovo oggetto chiamato PetStore. E questo non è un ottimo PetStore, quindi in pratica ti vendono solo un animale a caso (sì, lo so che questo è un esempio inventato).

public class PetStore
{
     public static IPet GetRandomPet()
     {    
          //Code to return a random Dog, Cat, or Mouse
     } 
}

IPet myNewRandomPet = PetStore.GetRandomPet();
myNewRandomPet.Sleep(10);

Il problema è che non sai che tipo di animale domestico sarà. Grazie all'interfaccia, sebbene tu sappia qualunque cosa sia Eat and Sleep.

Quindi questa risposta potrebbe non essere stata affatto utile, ma l'idea generale è che le interfacce ti permettono di fare cose ordinate come Iniezione di dipendenza e Inversione del controllo dove puoi ottenere un oggetto, avere un elenco ben definito di cose che l'oggetto può fare senza mai conoscere VERAMENTE quale sia il tipo concreto di quell'oggetto.

La risposta più semplice è che le interfacce definiscono cosa può fare la tua classe. È un & Quot; contratto & Quot; che dice che la tua classe sarà in grado di fare quell'azione.

Public Interface IRollOver
    Sub RollOver()
End Interface

Public Class Dog Implements IRollOver
    Public Sub RollOver() Implements IRollOver.RollOver
        Console.WriteLine("Rolling Over!")
    End Sub
End Class

Public Sub Main()
    Dim d as New Dog()
    Dim ro as IRollOver = TryCast(d, IRollOver)
    If ro isNot Nothing Then
        ro.RollOver()
    End If
End Sub

Fondamentalmente, stai garantendo che la classe Dog abbia sempre la possibilità di eseguire il rollover fintanto che continua a implementare quell'interfaccia. Se i gatti dovessero mai acquisire la capacità di RollOver (), anche loro potrebbero implementare quell'interfaccia e puoi trattare sia cani che gatti in modo omogeneo quando chiedono loro di RollOver ().

Quando guidi la macchina di un amico, sai più o meno come farlo. Questo perché tutte le auto convenzionali hanno un'interfaccia molto simile: volante, pedali e così via. Pensa a questa interfaccia come a un contratto tra case automobilistiche e conducenti. Come guidatore (l'utente / client dell'interfaccia in termini di software), non è necessario apprendere i dettagli di diverse auto per essere in grado di guidarli: ad esempio, tutto ciò che devi sapere è che girare il volante rende giro in auto. Come produttore di auto (il fornitore di un'implementazione dell'interfaccia in termini di software) hai una chiara idea di cosa dovrebbe avere la tua nuova auto e come dovrebbe comportarsi in modo che i conducenti possano usarli senza molta formazione aggiuntiva. Questo contratto è ciò che le persone nella progettazione del software chiamano disaccoppiamento (l'utente dal provider): il codice client è in termini di utilizzo di un'interfaccia piuttosto che di una sua particolare implementazione e quindi non è necessario conoscere i dettagli degli oggetti implementando l'interfaccia.

Le interfacce sono un meccanismo per ridurre l'accoppiamento tra parti diverse, possibilmente disparate di un sistema.

Dal punto di vista .NET

  • La definizione dell'interfaccia è un elenco di operazioni e / o proprietà.
  • I metodi di interfaccia sono sempre pubblici.
  • L'interfaccia stessa non deve essere pubblica.

Quando crei una classe che implementa l'interfaccia, devi fornire un'implementazione esplicita o implicita di tutti i metodi e proprietà definiti dall'interfaccia.

Inoltre, .NET ha un'unica eredità e le interfacce sono una necessità per un oggetto di esporre metodi ad altri oggetti che non sono a conoscenza o che non rientrano nella sua gerarchia di classi. Questo è anche noto come comportamenti di esposizione.

Un esempio un po 'più concreto:

Considera che abbiamo molti DTO (oggetti di trasferimento dati) che hanno proprietà per chi è stato aggiornato per ultimo e quando è stato. Il problema è che non tutti i DTO hanno questa proprietà perché non è sempre pertinente.

Allo stesso tempo desideriamo un meccanismo generico per garantire che queste proprietà siano impostate se disponibili quando inoltrate al flusso di lavoro, ma l'oggetto del flusso di lavoro dovrebbe essere liberamente accoppiato agli oggetti inviati. vale a dire che il metodo del flusso di lavoro di invio non dovrebbe davvero conoscere tutte le sottigliezze di ciascun oggetto e tutti gli oggetti nel flusso di lavoro non sono necessariamente oggetti DTO.

// First pass - not maintainable
void SubmitToWorkflow(object o, User u)
{
  if (o is StreetMap)
  {
     var map = (StreetMap)o;
     map.LastUpdated = DateTime.UtcNow;
     map.UpdatedByUser = u.UserID;
  }
  else if (o is Person)
  {
     var person = (Person)o;
     person.LastUpdated = DateTime.Now; // Whoops .. should be UtcNow
     person.UpdatedByUser = u.UserID;
  }
  // Whoa - very unmaintainable.

Nel codice sopra, SubmitToWorkflow() deve conoscere ogni singolo oggetto. Inoltre, il codice è un casino con un enorme if / else / switch, viola il non ripetere te stesso (DRY) e richiede agli sviluppatori di ricordare le modifiche di copia / incolla ogni volta che un nuovo oggetto viene aggiunto al sistema.

// Second pass - brittle
void SubmitToWorkflow(object o, User u)
{
  if (o is DTOBase)
  {
     DTOBase dto = (DTOBase)o;
     dto.LastUpdated = DateTime.UtcNow;
     dto.UpdatedByUser = u.UserID;
  }

È leggermente migliore, ma è ancora fragile. Se vogliamo inviare altri tipi di oggetti, abbiamo ancora bisogno di più dichiarazioni di casi. ecc.

// Third pass pass - also brittle
void SubmitToWorkflow(DTOBase dto, User u)
{
  dto.LastUpdated = DateTime.UtcNow;
  dto.UpdatedByUser = u.UserID;

È ancora fragile ed entrambi i metodi impongono il vincolo che tutti i DTO devono implementare questa proprietà che abbiamo indicato non era universalmente applicabile. Alcuni sviluppatori potrebbero essere tentati di scrivere metodi do-nothing, ma questo ha un cattivo odore. Non vogliamo che le classi fingano di supportare il monitoraggio degli aggiornamenti, ma non.

Interfacce, come possono aiutare?

Se definiamo un'interfaccia molto semplice:

public interface IUpdateTracked
{
  DateTime LastUpdated { get; set; }
  int UpdatedByUser { get; set; }
}

Qualsiasi classe che necessita di questo monitoraggio degli aggiornamenti automatici può implementare l'interfaccia.

public class SomeDTO : IUpdateTracked
{
  // IUpdateTracked implementation as well as other methods for SomeDTO
}

Il metodo del flusso di lavoro può essere reso molto più generico, più piccolo e più gestibile, e continuerà a funzionare indipendentemente da quante classi implementano l'interfaccia (DTO o altro) perché si occupa solo dell'interfaccia.

void SubmitToWorkflow(object o, User u)
{
  IUpdateTracked updateTracked = o as IUpdateTracked;
  if (updateTracked != null)
  {
     updateTracked.LastUpdated = DateTime.UtcNow;
     updateTracked.UpdatedByUser = u.UserID;
  }
  // ...
  • Possiamo notare che la variazione void SubmitToWorkflow(IUpdateTracked updateTracked, User u) garantirebbe la sicurezza del tipo, tuttavia non sembra rilevante in queste circostanze.

In alcuni codici di produzione che utilizziamo, abbiamo la generazione di codice per creare queste classi DTO dalla definizione del database. L'unica cosa che lo sviluppatore fa è creare correttamente il nome del campo e decorare la classe con l'interfaccia. Finché le proprietà sono chiamate LastUpdated e UpdatedByUser, funziona e basta.

Forse stai chiedendo Cosa succede se il mio database è legacy e questo non è possibile? Devi solo fare un po 'più di battitura; un'altra grande caratteristica delle interfacce è che possono permetterti di creare un ponte tra le classi.

Nel codice seguente abbiamo un LegacyDTO fittizio, un oggetto preesistente con campi con nomi simili. Sta implementando l'interfaccia IUpdateTracked per colmare le proprietà esistenti, ma con nomi diversi.

// Using an interface to bridge properties
public class LegacyDTO : IUpdateTracked
{
    public int LegacyUserID { get; set; }
    public DateTime LastSaved { get; set; }

    public int UpdatedByUser
    {
        get { return LegacyUserID; }
        set { LegacyUserID = value; }
    }
    public DateTime LastUpdated
    {
        get { return LastSaved; }
        set { LastSaved = value; }
    }
}

Potresti pensare Fantastico, ma non è confuso avere più proprietà? o Cosa succede se ci sono già quelle proprietà, ma significano qualcos'altro? .NET dàla possibilità di implementare esplicitamente l'interfaccia.

Ciò significa che le proprietà IUpdateTracked saranno visibili solo quando utilizziamo un riferimento a IUpdateTracked. Nota come non vi siano modificatori pubblici sulla dichiarazione e la dichiarazione includa il nome dell'interfaccia.

// Explicit implementation of an interface
public class YetAnotherObject : IUpdatable
{
    int IUpdatable.UpdatedByUser
    { ... }
    DateTime IUpdatable.LastUpdated
    { ... }

Avere così tanta flessibilità nel definire come la classe implementa l'interfaccia offre allo sviluppatore molta libertà di separare l'oggetto dai metodi che lo consumano. Le interfacce sono un ottimo modo per interrompere l'accoppiamento.

C'è molto di più nelle interfacce oltre a questo. Questo è solo un esempio di vita reale semplificato che utilizza un aspetto della programmazione basata su interfaccia.

Come accennato in precedenza, e da altri responder, è possibile creare metodi che accettano e / o restituiscono riferimenti all'interfaccia anziché un riferimento di classe specifico. Se avessi bisogno di trovare duplicati in un elenco, potrei scrivere un metodo che accetta e restituisce un IList (un'interfaccia che definisce le operazioni che funzionano sugli elenchi) e non sono vincolato a una classe di raccolta concreta.

// Decouples the caller and the code as both
// operate only on IList, and are free to swap
// out the concrete collection.
public IList<T> FindDuplicates( IList<T> list )
{
    var duplicates = new List<T>()
    // TODO - write some code to detect duplicate items
    return duplicates;
}

Avvertenza sul controllo delle versioni

Se si tratta di un'interfaccia pubblica, stai dichiarando Garantisco che l'interfaccia x assomigli a questa! E una volta che hai spedito il codice e pubblicato l'interfaccia, non dovresti mai cambiarlo. Non appena il codice consumatore inizia a fare affidamento su tale interfaccia, non si desidera interrompere il loro codice nel campo.

Vedi questo post Haacked per una buona discussione.

Interfacce contro classi astratte (base)

Le classi astratte possono fornire implementazione, mentre le interfacce no. Le classi astratte sono in qualche modo più flessibili nell'aspetto della versione se segui alcune linee guida come il modello NVPI (Non-Virtual Public Interface).

Vale la pena ribadire che in .NET una classe può ereditare solo da una singola classe, ma una classe può implementare tutte le interfacce che desidera.

Iniezione delle dipendenze

Il breve riepilogo delle interfacce e dell'iniezione delle dipendenze (DI) è che l'uso delle interfacce consente agli sviluppatori di scrivere codice programmato su un'interfaccia per fornire servizi. In pratica puoi finire con molte piccole interfacce e piccole classi, e un'idea è che le piccole classi che fanno una cosa e solo una cosa sono molto più facili da programmare e mantenere.

class AnnualRaiseAdjuster
   : ISalaryAdjuster
{
   AnnualRaiseAdjuster(IPayGradeDetermination payGradeDetermination) { ...  }

   void AdjustSalary(Staff s)
   {
      var payGrade = payGradeDetermination.Determine(s);
      s.Salary = s.Salary * 1.01 + payGrade.Bonus;
   }
}

In breve, il vantaggio mostrato nello snippet sopra è che la determinazione del grado di retribuzione viene appena iniettata nel regolatore annuale del rilancio. Il modo in cui viene determinato il grado di retribuzione non ha importanza per questa classe. Durante il test, lo sviluppatore può prendere in giro i risultati della determinazione del grado di retribuzione per garantire che il regolatore salariale funzioni come desiderato. I test sono veloci anche perché testano solo la classe e non tutto il resto.

Questo non è un primer DI, anche se ci sono libri interi dedicati all'argomento; l'esempio sopra è molto semplificato.

Questo è un piuttosto " long " soggetto, ma lasciami provare a dirlo in modo semplice.

Un'interfaccia è -as " la chiamano " - un contratto. Ma dimentica quella parola.

Il modo migliore per capirli è attraverso una sorta di esempio di pseudo-codice. È così che li ho capiti molto tempo fa.

Supponi di avere un'app che elabora i messaggi. Un messaggio contiene alcune cose, come un soggetto, un testo, ecc.

Quindi scrivi MessageController per leggere un database ed estrarre i messaggi. È molto bello finché non si sente improvvisamente che presto verranno implementati anche i fax. Quindi ora dovrai leggere & Quot; Fax & Quot; ed elaborarli come messaggi!

Questo potrebbe facilmente trasformarsi in un codice Spagetti. Quindi cosa fai invece di avere un MessageController che controlla & Quot; Messaggi & Quot; solo, lo rendi in grado di funzionare con una interfaccia chiamata IMessage (l'I è solo un uso comune, ma non richiesto).

L'interfaccia di IMessage contiene alcuni dati di base di cui hai bisogno per assicurarti di essere in grado di elaborare il messaggio in quanto tale.

Quindi, quando crei le tue classi EMail, Fax, PhoneCall, le rendi Implementa l'interfaccia chiamata IMessage .

Quindi nel MessageController, puoi avere un metodo chiamato così:

private void ProcessMessage(IMessage oneMessage)
{
    DoSomething();
}

Se non avessi usato le interfacce, dovresti avere:

private void ProcessEmail(Email someEmail);
private void ProcessFax(Fax someFax);
etc.

Quindi, usando un'interfaccia comune , ti sei appena assicurato che il metodo ProcessMessage sarà in grado di lavorare con esso, indipendentemente dal fatto che fosse un fax, un'e-mail una chiamata telefonica, ecc.

Perché o come ?

Poiché l'interfaccia è un contratto che specifica alcune cose che devi rispettare (o implementare) per poterlo utilizzare. Pensalo come un badge . Se il tuo oggetto & Quot; Fax & Quot; non ha l'interfaccia IMessage, quindi il tuo metodo ProcessMessage non sarebbe in grado di funzionare con quello, ti darà un tipo non valido, perché stai passando un fax a un metodo che prevede un oggetto IMessage.

Vedi il punto?

Pensa all'interfaccia come a " subset " di metodi e proprietà che avrai a disposizione, nonostante il tipo di oggetto reale. Se l'oggetto originale (Fax, Email, PhoneCall, ecc.) Implementa tale interfaccia, è possibile passarlo in sicurezza attraverso i metodi che richiedono tale interfaccia.

C'è più magia nascosta lì dentro, puoi lanciare le interfacce sui loro oggetti originali:

Fax myFax = (Fax) SomeIMessageThatIReceive;

ArrayList () in .NET 1.1 aveva una bella interfaccia chiamata IList. Se avessi un IList (molto & Quot; generico & Quot;) potresti trasformarlo in un ArrayList:

ArrayList ar = (ArrayList)SomeIList;

E ci sono migliaia di campioni là fuori in natura.

Interfacce come ISortable, IComparable, ecc., definiscono i metodi e le proprietà che devi implementare nella tua classe per raggiungere quella funzionalità.

Per espandere il nostro esempio, potresti avere un Elenco < > di e-mail, fax, chiamata telefonica, tutti nella stessa lista, se il tipo è IMessage, ma non potresti averli tutti insieme se gli oggetti fossero semplicemente e-mail, fax, ecc.

Se desideri ordinare (o enumerare, ad esempio) i tuoi oggetti, ti serviranno per implementare l'interfaccia corrispondente. Nell'esempio .NET, se si dispone di un elenco di & Quot; Fax & Quot; oggetti e vuoi essere in grado di ordinarli usando MyList.Sort (), devi rendere la tua classe di fax in questo modo:

public class Fax : ISorteable 
{
   //implement the ISorteable stuff here.
}

Spero che questo ti dia un suggerimento. Altri utenti probabilmente pubblicheranno altri buoni esempi. In bocca al lupo! e abbraccia il potere di INTERFaces.

avvertimento : non tutto va bene con le interfacce, ci sono alcuni problemi, i puristi di OOP inizieranno una guerra su questo. Rimarrò da parte. Uno svantaggio di un Interfce (almeno in .NET 2.0) è quellonon puoi avere membri PRIVATI o protetti, deve essere pubblico. Questo ha un senso, ma a volte vorresti semplicemente dichiarare le cose come private o protette.

Oltre alle funzioni che le interfacce hanno nei linguaggi di programmazione, sono anche un potente strumento semantico quando esprimono idee di design ad altre persone .

Una base di codice con interfacce ben progettate è improvvisamente molto più facile da discutere. " Sì, è necessario un CredentialsManager per registrare nuovi server remoti. " " Passa una PropertyMap a ThingFactory per ottenere un'istanza funzionante. "

La capacità di affrontare una cosa complessa con una sola parola è piuttosto utile.

Le interfacce ti consentono di codificare gli oggetti in modo generico. Ad esempio, supponiamo che tu abbia un metodo che invia rapporti. Ora supponiamo che tu abbia un nuovo requisito in cui devi scrivere un nuovo rapporto. Sarebbe bello se potessi riutilizzare il metodo che hai già scritto giusto? Le interfacce rendono tutto così semplice:

interface IReport
{
    string RenderReport();
}

class MyNewReport : IReport
{
    public string RenderReport()
    {
        return "Hello World Report!";

    }
}

class AnotherReport : IReport
{
    public string RenderReport()
    {
        return "Another Report!";

    }
}

//This class can process any report that implements IReport!
class ReportEmailer()
{
     public void EmailReport(IReport report)
     {
         Email(report.RenderReport());
     }
}

class MyApp()
{
    void Main()
    {
        //create specific "MyNewReport" report using interface
        IReport newReport = new MyNewReport();

        //create specific "AnotherReport" report using interface
        IReport anotherReport = new AnotherReport();

        ReportEmailer reportEmailer = new ReportEmailer();

        //emailer expects interface
        reportEmailer.EmailReport(newReport);
        reportEmailer.EmailReport(anotherReport);



    }

}

Le interfacce sono anche la chiave del polimorfismo, uno dei " TRE PILASTRI DI OOD " ;.

Alcune persone l'hanno toccato sopra, il polimorfismo significa solo che una determinata classe può assumere diverse " forme " ;. Significato, se abbiamo due classi, & Quot; Cane & Quot; e " Cat " ed entrambi implementano l'interfaccia " INeedFreshFoodAndWater " (hehe) - il tuo codice può fare qualcosa del genere (pseudocodice):

INeedFreshFoodAndWater[] array = new INeedFreshFoodAndWater[];
array.Add(new Dog());
array.Add(new Cat());

foreach(INeedFreshFoodAndWater item in array)
{
   item.Feed();
   item.Water();
}

Questo è potente perché ti consente di trattare in modo astratto diverse classi di oggetti e ti consente di fare cose come rendere i tuoi oggetti più liberamente accoppiati, ecc.

OK, quindi si tratta di classi astratte vs. interfacce ...

Concettualmente, le classi astratte sono lì per essere usate come classi base. Molto spesso essi stessi forniscono già alcune funzionalità di base e le sottoclassi devono fornire la propria implementazione dei metodi astratti (quelli sono i metodi che non sono implementati nella classe base astratta).

Le interfacce sono principalmente utilizzate per disaccoppiare il codice client dai dettagli di una particolare implementazione. Inoltre, a volte la possibilità di cambiare l'implementazione senza modificare il codice client rende il codice client più generico.

A livello tecnico, è più difficile tracciare la linea tra le classi astratte e le interfacce, perché in alcuni linguaggi (ad esempio C ++) non esiste alcuna differenza sintattica o perché è possibile utilizzare anche classi astratte ai fini del disaccoppiamento o della generalizzazione . L'uso di una classe astratta come interfaccia è possibile perché ogni classe base, per definizione, definisce un'interfaccia che tutte le sue sottoclassi dovrebbero onorare (cioè, dovrebbe essere possibile usare una sottoclasse anziché una classe base).

Le interfacce sono un modo per imporre che un oggetto implementa una certa quantità di funzionalità, senza dover usare l'ereditarietà (che porta a un codice fortemente accoppiato, anziché accoppiato liberamente che può essere ottenuto usando interfacce).

Le interfacce descrivono la funzionalità, non l'implementazione.

La maggior parte delle interfacce che trovi sono una raccolta di firme di metodi e proprietà. Chiunque implementa un'interfaccia deve fornire le definizioni di ciò che è sempre nell'interfaccia.

In poche parole: un'interfaccia è una classe definita da metodi ma non implementata in essi. Al contrario, una classe astratta ha implementato alcuni dei metodi, ma non tutti.

Pensa a un'interfaccia come a un contratto. Quando una classe implementa un'interfaccia, in sostanza accetta di onorare i termini di quel contratto. Come consumatore, ti importa solo che gli oggetti che possiedi possano adempiere ai loro doveri contrattuali. I loro meccanismi e dettagli interni non sono importanti.

Un buon motivo per usare un'interfaccia rispetto a una classe astratta in Java è che una sottoclasse non può estendere più classi base, ma PUO 'implementare più interfacce.

Java non consente l'ereditarietà multipla (per ottime ragioni, cerca un terribile diamante) ma cosa succede se vuoi che la tua classe fornisca diversi insiemi di comportamenti? Di 'che vuoi che chiunque lo usi sappia che può essere serializzato e che può dipingere se stesso sullo schermo. la risposta è implementare due diverse interfacce.

Poiché le interfacce non contengono alcuna implementazione propria e nessun membro di istanza, è sicuro implementarne diverse nella stessa classe senza ambiguità.

Il lato negativo è che dovrai avere l'implementazione in ogni classe separatamente. Quindi, se la tua gerarchia è semplice e ci sono parti dell'implementazione che dovrebbero essere le stesse per tutte le classi ereditarie, usa una classe astratta.

Supponendo che ti riferisci alle interfacce in linguaggi orientati agli oggetti tipicamente staticamente, l'uso principale è quello di affermare che la tua classe segue un contratto o un protocollo particolare.

Supponi di avere:

public interface ICommand
{
    void Execute();
}
public class PrintSomething : ICommand
{
    OutputStream Stream { get; set; }
    String Content {get; set;}
    void Execute()
    { 
        Stream.Write(content);
    }
}

Ora hai una struttura di comando sostituibile. Qualsiasi istanza di una classe che implementa IExecute può essere memorizzata in un elenco di un qualche tipo, dire qualcosa che implementa IEnumerable e si può scorrere attraverso quello ed eseguire ognuno, sapendo che ogni oggetto farà solo la cosa giusta. Puoi creare un comando composito implementando CompositeCommand che avrà il suo elenco di comandi da eseguire, o un LoopingCommand per eseguire ripetutamente un set di comandi, quindi avrai la maggior parte di un semplice interprete.

Quando puoi ridurre un insieme di oggetti a un comportamento che tutti hanno in comune, potresti avere motivo di estrarre un'interfaccia. Inoltre, a volte è possibile utilizzare le interfacce per impedire agli oggetti di intromettersi accidentalmente sulle preoccupazioni di quella classe; ad esempio, è possibile implementare un'interfaccia che consenta solo ai client di recuperare, anziché modificare i dati nel proprio oggetto, e che la maggior parte degli oggetti riceva solo un riferimento all'interfaccia di recupero.

Le interfacce funzionano meglio quando le tue interfacce sono relativamente semplici e fanno poche ipotesi.

Cerca il principio di abbonamento a Liskov per dare più senso a questo.

Alcuni linguaggi tipicamente statici come C ++ non supportano le interfacce come concetto di prima classe, quindi crei interfacce usando pure classi astratte.

Aggiorna Dato che sembra che tu stia chiedendo informazioni su classi astratte e interfacce, ecco la mia semplificazione eccessiva preferita:

  • Le interfacce definiscono capacità e caratteristiche.
  • Le classi astratte definiscono le funzionalità di base.

In genere, eseguo un refactoring dell'interfaccia di estrazione prima di creare una classe astratta. Sono più propenso a costruire una classe astratta se penso che ci dovrebbe essere un contratto di creazione (in particolare, che un tipo specifico di costruttore dovrebbe essere sempre supportato da sottoclassi). Tuttavia, uso raramente & Quot; pure & Quot; classi astratte in C # / java. Sono molto più propenso a implementare una classe con almeno un metodo contenente un comportamento significativo e utilizzare metodi astratti per supportare i metodi modello chiamati da quel metodo. Quindi la classe astratta è un'implementazione di base di un comportamento, che tutte le sottoclassi concrete possono sfruttare senza dover reimplementare.

Risposta semplice: un'interfaccia è un insieme di firme di metodi (+ tipo restituito). Quando un oggetto dice che implementa un'interfaccia, sai che espone quel set di metodi.

Le interfacce sono un modo per implementare le convenzioni in un modo ancora fortemente tipizzato e polimorfico.

Un buon esempio del mondo reale sarebbe IDisposable in .NET. Una classe che implementa l'interfaccia IDisposable impone a quella classe di implementare il metodo Dispose (). Se la classe non implementa Dispose () otterrai un errore del compilatore quando provi a compilare. Inoltre, questo modello di codice:

using (DisposableClass myClass = new DisposableClass())
  {
  // code goes here
  }

Causa myClass.Dispose () da eseguire automaticamente quando l'esecuzione esce dal blocco interno.

Tuttavia, e questo è importante, non esiste alcuna applicazione su ciò che dovrebbe fare il metodo Dispose (). Potresti avere il tuo metodo Dispose () per scegliere ricette casuali da un file e inviarle via e-mail a una lista di distribuzione, al compilatore non importa. L'intento del modello IDisposable è di semplificare la pulizia delle risorse. Se le istanze di una classe manterranno gli handle di file, IDisposable rende molto semplice centralizzare la deallocazione e il codice di cleanup in un punto e promuovere uno stile di utilizzo che garantisca che la deallocazione si verifichi sempre.

E questa è la chiave per le interfacce. Sono un modo per semplificare le convenzioni di programmazione e i modelli di progettazione. Che, se usato correttamente, promuove un codice più semplice e auto-documentante che è più facile da usare, più facile da mantenere e più corretto.

Ecco un esempio di db che uso spesso. Diciamo che hai un oggetto e un oggetto contenitore come un elenco. Supponiamo che a volte potresti voler memorizzare gli oggetti in una sequenza particolare. Si supponga che la sequenza non sia correlata alla posizione nell'array ma che gli oggetti siano un sottoinsieme di un set più grande di oggetti e che la posizione della sequenza sia correlata al filtro sql del database.

Per tenere traccia delle posizioni della sequenza personalizzate, è possibile rendere l'oggetto implementare un'interfaccia personalizzata. L'interfaccia personalizzata potrebbe mediare lo sforzo organizzativo richiesto per mantenere tali sequenze.

Ad esempio, la sequenza che ti interessa non ha nulla a che fare con le chiavi primarie nei record. Con l'oggetto che implementa l'interfaccia potresti dire myObject.next () o myObject.prev ().

Ho avuto lo stesso problema con te e trovo il " contratto " spiegazione un po 'confusa.

Se si specifica che un metodo accetta un'interfaccia IEnumerable come parametro in, si potrebbe dire che si tratta di un contratto che specifica che il parametro deve essere di un tipo che eredita dall'interfaccia IEnumerable e quindi supporta tutti i metodi specificati in IEnumerable interfaccia. Lo stesso sarebbe vero se usassimo una classe astratta o una classe normale. Qualsiasi oggetto che eredita da quelle classi sarebbe ok per passare come parametro. Saresti comunque in grado di dire che l'oggetto ereditato supporta tutti i metodi pubblici nella classe base indipendentemente dal fatto che la classe base sia una classe normale, una classe astratta o un'interfaccia.

Una classe astratta con tutti i metodi astratti è sostanzialmente la stessa di un'interfaccia, quindi si potrebbe dire che un'interfaccia è semplicemente una classe senza metodi implementati. Potresti effettivamente eliminare le interfacce dal linguaggio e usare semplicemente la classe astratta con solo metodi astratti. Penso che il motivo per cui li separiamo sia per motivi semantici, ma per motivi di codifica non vedo il motivo e lo trovo solo confuso.

Un altro suggerimento potrebbe essere quello di rinominare l'interfaccia in una classe di interfaccia poiché l'interfaccia è solo un'altra variante di una classe.

In alcune lingue ci sono sottili differenze che consentono a una classe di ereditare solo 1 classe ma interfacce multiple mentre in altre potresti avere molte di entrambe, ma questo è un altro problema e non direttamente correlato penso

Il modo più semplice per comprendere le interfacce è iniziare considerando il significato dell'eredità di classe. Comprende due aspetti:

  1. I membri di una classe derivata possono utilizzare i membri pubblici o protetti di una classe base come propri.
  2. I membri di una classe derivata possono essere utilizzati dal codice che prevede un membro della classe base (nel senso che sono sostituibili).

Entrambe queste funzionalità sono utili, ma poiché è difficile consentire a una classe di utilizzare membri di più di una classe come propria, molti linguaggi e framework consentono solo alle classi di ereditare da una singola classe base. D'altra parte, non vi è alcuna difficoltà particolare nell'avere una classe sostituibile con più altre cose non correlate.

Inoltre, poiché il primo vantaggio dell'ereditarietà può essere ampiamente conseguito tramite l'incapsulamento, il vantaggio relativo derivante dal consentire l'ereditarietà multipla del primo tipo è alquanto limitato. D'altro canto, essere in grado di sostituire un oggetto con più tipi di cose non correlati è un'abilità utile che non può essere facilmente raggiunta senza il supporto del linguaggio.

Le interfacce forniscono un mezzo con cui un linguaggio / framework può consentire ai programmi di beneficiare del secondo aspetto dell'ereditarietà per più tipi di base, senza che sia necessario che fornisca anche il primo.

L'interfaccia è come una classe completamente astratta. Cioè, una classe astratta con solo membri astratti. Puoi anche implementare più interfacce, è come ereditare da più classi completamente astratte. Comunque ... questa spiegazione aiuta solo se capisci cos'è una classe astratta.

Come altri hanno già detto qui, le interfacce definiscono un contratto (come le classi che usano l'interfaccia " look ") e le classi astratte definiscono la funzionalità condivisa.

Vediamo se il codice aiuta:

public interface IReport
{
    void RenderReport(); // This just defines the method prototype
}

public abstract class Reporter
{
    protected void DoSomething()
    {
        // This method is the same for every class that inherits from this class
    }
}

public class ReportViolators : Reporter, IReport
{
    public void RenderReport()
    {
        // Some kind of implementation specific to this class
    }
}

public class ClientApp
{
    var violatorsReport = new ReportViolators();

    // The interface method
    violatorsReport.RenderReport();

    // The abstract class method
    violatorsReport.DoSomething();
}

Le interfacce richiedono che qualsiasi classe che le implementi contenga i metodi definiti nell'interfaccia.

Lo scopo è che, senza dover vedere il codice in una classe, puoi sapere se può essere usato per un determinato compito. Ad esempio, la classe Integer in Java implementa l'interfaccia comparabile, quindi, se vedessi solo l'intestazione del metodo (la stringa pubblica implementa Comparable), sapresti che contiene un metodo compareTo ().

Nel tuo caso semplice, potresti ottenere qualcosa di simile a quello che ottieni con le interfacce usando una classe base comune che implementa show() (o forse lo definisce come astratto). Vorrei cambiare i tuoi nomi generici in qualcosa di più concreto, Eagle e Hawk invece di MyClass1 e MyClass2 . In tal caso, potresti scrivere codice come

Bird bird = GetMeAnInstanceOfABird(someCriteriaForSelectingASpecificKindOfBird);
bird.Fly(Direction.South, Speed.CruisingSpeed);

Ciò ti consente di scrivere codice in grado di gestire qualsiasi cosa sia Bird . È quindi possibile scrivere un codice che induca Uccello a fare le sue cose (volare, mangiare, deporre le uova e così via) che agisce su un'istanza che considera un Uccello . Quel codice funzionerebbe se Uccello è in realtà un Aquila , Falco o qualsiasi altra cosa che derivi da Uccello .

Quel paradigma inizia a diventare confuso, tuttavia, quando non hai una vera è una relazione . Supponi di voler scrivere un codice che voli le cose nel cielo. Se scrivi quel codice per accettare una classe base Bird , diventa improvvisamente difficile evolvere quel codice per lavorare su un'istanza JumboJet , perché mentre un Bird e un JumboJet possono certamente volare entrambi, un JumboJet non è certamente un Uccello .

Entra nell'interfaccia.

Ciò che Uccello (e Aquila e Falco ) hanno è che possono volare tutti . Se invece scrivi il codice sopra per agire su un'interfaccia, IFly , quel codice può essere applicato a tutto ciò che fornisce un'implementazione a quell'interfaccia.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top