Delegato vs Interfacce-Altri chiarimenti disponibili?

23

Dopo aver letto l'articolo- Quando utilizzare i delegati anziché le interfacce (Guida alla programmazione C #) , Ho bisogno di aiuto per capire i punti indicati sotto, che ho trovato non così chiaro (per me). Qualche esempio o spiegazione dettagliata disponibile per questi?

Utilizza un delegato quando:

  • Viene utilizzato un modello di progettazione con evento.
  • È auspicabile incapsulare un metodo statico.
  • È richiesta una composizione semplice.
  • Una classe potrebbe richiedere più di un'implementazione del metodo.

Utilizza un'interfaccia quando:

  • Esiste un gruppo di metodi correlati che possono essere chiamati.
  • Una classe richiede solo un'implementazione del metodo.

Le mie domande sono,

  1. Che cosa intendono con un modello di progettazione evento?
  2. In che modo la composizione risulta facile se si utilizza un delegato?
  3. se esiste un gruppo di metodi correlati che possono essere chiamati, quindi utilizzare l'interfaccia: quali vantaggi ha?
  4. se una classe richiede solo un'implementazione del metodo, usa l'interfaccia: come è giustificata in termini di benefici?
posta WinW 13.10.2011 - 12:40
fonte

9 risposte

11

Che cosa intendono con un modello di progettazione evento?

Molto probabilmente si riferiscono a un'implementazione del modello di osservatore che è un costrutto del linguaggio di base in C #, esposto come ' eventi ". È possibile ascoltare gli eventi collegando loro un delegato. Come ha sottolineato Yam Marcovic, EventHandler è il tipo di delegato di base convenzionale per gli eventi, ma è possibile utilizzare qualsiasi tipo di delegato.

In che modo la composizione risulta facile se si utilizza un delegato?

Questo probabilmente si riferisce solo all'offerta dei delegati di flessibilità. Puoi facilmente "comporre" determinati comportamenti. Con l'aiuto di lambdas , la sintassi per fare ciò è anche molto concisa. Considera il seguente esempio.

class Bunny
{
    Func<bool> _canHop;

    public Bunny( Func<bool> canHop )
    {
        _canHop = canHop;
    }

    public void Hop()
    {
        if ( _canHop() )  Console.WriteLine( "Hop!" );
    }
}

Bunny captiveBunny = new Bunny( () => IsBunnyReleased );
Bunny lazyBunny = new Bunny( () => !IsLazyDay );
Bunny captiveLazyBunny = new Bunny( () => IsBunnyReleased && !IsLazyDay );

Fare qualcosa di simile con le interfacce richiederebbe di utilizzare il modello di strategia o di utilizzare una base (astratta) Bunny di classe dalla quale estendi conigli più specifici.

se esiste un gruppo di metodi correlati che possono essere chiamati, quindi utilizzare l'interfaccia: quali vantaggi ha?

Ancora una volta, userò i coniglietti per dimostrare come sarebbe più semplice.

interface IAnimal
{
    void Jump();
    void Eat();
    void Poo();
}

class Bunny : IAnimal { ... }
class Chick : IAnimal { ... }

// Using the interface.
IAnimal bunny = new Bunny();
bunny.Jump();  bunny.Eat();  bunny.Poo();
IAnimal chick = new Chick();
chick.Jump();  chick.Eat();  chick.Poo();

// Without the interface.
Action bunnyJump = () => bunny.Jump();
Action bunnyEat = () => bunny.Eat();
Action bunnyPoo = () => bunny.Poo();
bunnyJump(); bunnyEat(); bunnyPoo();
Action chickJump = () => chick.Jump();
Action chickEat = () => chick.Eat();
...

se una classe richiede solo un'implementazione del metodo, usa l'interfaccia: come è giustificata in termini di benefici?

Per questo, considera di nuovo il primo esempio con il coniglio. Se è necessaria solo un'implementazione, non è mai necessaria alcuna composizione personalizzata, è possibile esporre questo comportamento come un'interfaccia. Non dovrai mai costruire i lambda, puoi semplicemente usare l'interfaccia.

Conclusione

I delegati offrono molta più flessibilità, mentre le interfacce aiutano a stabilire contratti forti. Quindi trovo l'ultimo punto menzionato, "Una classe potrebbe aver bisogno di più di un'implementazione del metodo." , di gran lunga il più rilevante.

Un ulteriore motivo per utilizzare i delegati è quando vuoi esporre solo parte di una classe che non puoi modificare dal file sorgente.

Come esempio di tale scenario (massima flessibilità, nessuna necessità di modificare le fonti), considera questa implementazione dell'algoritmo di ricerca binaria per qualsiasi possibile raccolta semplicemente passando due delegati.

    
risposta data 19.10.2011 - 00:47
fonte
12

Un delegato è qualcosa come un'interfaccia per una singola firma del metodo, che non deve essere implementata esplicitamente come un'interfaccia regolare. Puoi costruirlo in corsa.

Un'interfaccia è solo una struttura linguistica che rappresenta un contratto - "Con la presente garantisco di rendere disponibili i seguenti metodi e proprietà".

Inoltre, non sono completamente d'accordo sul fatto che i delegati siano principalmente utili se usati come soluzione per il modello osservatore / sottoscrittore. È, tuttavia, una soluzione elegante al "problema di prolattità java".

Per le tue domande:

1 & 2)

Se si desidera creare un sistema di eventi in Java, in genere si utilizza un'interfaccia per propagare l'evento, ad esempio:

interface KeyboardListener
{
    void KeyDown(int key);
    void KeyUp(int key)
    void KeyPress(int key);
    .... and so on
}

Questo significa che la tua classe dovrà implementare esplicitamente tutti questi metodi e fornire stub per tutti loro anche se vuoi semplicemente implementare KeyPress(int key) .

In C #, questi eventi verrebbero rappresentati come elenchi di delegati, nascosti dalla parola chiave "evento" in c #, uno per ciascun evento singolare. Ciò significa che puoi iscriverti facilmente a ciò che desideri senza appesantire la tua classe con metodi "chiave" pubblici ecc.

+1 per i punti 3-5.

Inoltre:

I delegati sono molto utili quando si desidera fornire ad esempio una funzione "mappa", che prende una lista e proietta ogni elemento in una nuova lista, con la stessa quantità di elementi, ma in qualche modo diversi. In sostanza, IEnumerable.Select (...).

IEnumerable.Select prende un Func<TSource, TDest> , che è un delegato che avvolge una funzione che prende un elemento TSource e trasforma quell'elemento in un elemento TDest.

In Java questo dovrebbe essere implementato usando un'interfaccia. Spesso non esiste un luogo naturale per implementare tale interfaccia. Se una classe contiene una lista che vuole trasformare in qualche modo, non è molto naturale implementare l'interfaccia "ListTransformer", specialmente perché potrebbero esserci due liste diverse che dovrebbero essere trasformate in modi diversi.

Naturalmente, potresti usare le classi anonime che sono un concetto simile (in java).

    
risposta data 13.10.2011 - 13:57
fonte
3

1) Modelli di eventi, benché il classico sia il pattern Observer, ecco un buon link Microsoft Microsoft talk Observer

L'articolo a cui ti sei collegato non è molto ben scritto e rende le cose più complicate del necessario. Il business statico è di buon senso, non è possibile definire un'interfaccia con i membri statici, quindi se si desidera un comportamento polimorfico su un metodo statico si utilizzerà un delegato.

Il link sopra parla di delegati e perché ci sono buoni per la composizione, quindi questo potrebbe aiutare con la tua query.

    
risposta data 13.10.2011 - 13:57
fonte
3

Prima di tutto, bella domanda. Ammiro la tua attenzione sull'utilità piuttosto che accettare ciecamente le "migliori pratiche". +1 per quello.

Ho letto questa guida prima. Devi ricordare qualcosa a riguardo - è solo una guida, principalmente per i nuovi arrivati di C # che sanno come programmare ma non hanno molta familiarità con il modo in cui C # fa le cose. Non è tanto una pagina di regole quanto una pagina che descrive come le cose sono già fatte normalmente. E poiché sono già fatti in questo modo ovunque, potrebbe essere una buona idea rimanere coerenti.

Arriverò al punto, rispondendo alle tue domande.

Prima di tutto, presumo che tu sappia già cos'è un'interfaccia. Per quanto riguarda un delegato, è sufficiente dire che si tratta di una struttura contenente un puntatore digitato a un metodo, insieme a un puntatore facoltativo all'oggetto che rappresenta l'argomento this per quel metodo. In caso di metodi statici, il secondo puntatore è nullo.
Ci sono anche delegati Multicast, che sono simili ai delegati, ma possono avere diverse di queste strutture assegnate a loro (significa che una singola chiamata a Invoke su un delegato multicast richiama tutti i metodi nella sua lista di invocazione assegnata).

Che cosa intendono con un modello di progettazione evento?

Significano usare eventi in C # (che ha parole chiave speciali per implementare in modo sofisticato questo schema estremamente utile). Gli eventi in C # sono alimentati dai delegati multicast.

Quando definisci un evento, come in questo esempio:

class MyClass {
  // Note: EventHandler is just a multicast delegate,
  // that returns void and accepts (object sender, EventArgs e)!
  public event EventHandler MyEvent;

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

Il compilatore in realtà trasforma questo nel seguente codice:

class MyClass {
  private EventHandler MyEvent = null;

  public void add_MyEvent(EventHandler value) {
    MyEvent += value;
  }

  public void remove_MyEvent(EventHandler value) {
    MyEvent -= value;
  }

  public void DoSomethingThatTriggersMyEvent() {
    // ... some code
    var handler = MyEvent;
    if (handler != null)
      handler(this, EventArgs.Empty);
    // ... some other code
  }
}

Quindi iscriviti a un evento facendo

MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;

Che compila fino a

MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));

Quindi questo è eventing in C # (o .NET in generale).

In che modo la composizione risulta facile se si utilizza un delegato?

Questo può essere facilmente dimostrato:

Supponiamo di avere una classe che dipende da un insieme di azioni da passarvi. Puoi incapsulare quelle azioni in un'interfaccia:

interface RequiredMethods {
  void DoX();
  int DoY();
};

E chiunque volesse passare delle azioni alla tua classe avrebbe dovuto prima implementare quell'interfaccia. Oppure potresti semplificarti la vita in base alla seguente classe:

sealed class RequiredMethods {
  public Action DoX;
  public Func<int> DoY();
}

In questo modo i chiamanti devono solo creare un'istanza di RequiredMethods e associare metodi ai delegati in fase di runtime. Questo è di solito più semplice.

Questo modo di fare le cose è estremamente utile nelle giuste circostanze. Pensaci: perché dipendere da un'interfaccia quando tutto quello che ti interessa è avere una implementazione passata?

Vantaggi dell'uso delle interfacce quando esiste un gruppo di metodi correlati

È vantaggioso utilizzare le interfacce perché le interfacce normalmente richiedono implementazioni esplicite in fase di compilazione. Questo significa che crei una nuova classe.
E se si dispone di un gruppo di metodi correlati in un singolo pacchetto, è utile che il pacchetto sia riusabile da altre parti del codice. Quindi, se possono semplicemente istanziare una classe invece di creare una serie di delegati, è più semplice.

Vantaggi dell'uso di interfacce se una classe richiede solo un'implementazione

Come notato in precedenza, le interfacce vengono implementate in fase di compilazione, il che significa che sono più efficienti rispetto al richiamo di un delegato (che è di per sé un livello di riferimento indiretto).

"Un'implementazione" potrebbe significare un'implementazione che esiste un singolo posto ben definito.
Altrimenti un'implementazione potrebbe provenire da qualsiasi parte del programma che capita semplicemente conforme alla firma del metodo. Ciò consente una maggiore flessibilità, poiché i metodi devono solo conformarsi alla firma prevista, piuttosto che appartenere a una classe che implementa esplicitamente un'interfaccia specifica. Ma questa flessibilità potrebbe comportare un costo e in realtà infrange il Principio di sostituzione di Liskov , perché la maggior parte volte vuoi essere esplicito, perché minimizza la possibilità di incidenti. Proprio come la tipizzazione statica.

Il termine potrebbe anche riferirsi ai delegati multicast qui. I metodi dichiarati dalle interfacce possono essere implementati solo una volta in una classe di implementazione. Ma i delegati possono accumulare più metodi, che saranno chiamati sequenzialmente.

Quindi, tutto sommato, sembra che la guida non sia sufficientemente informativa e funzioni semplicemente come è: una guida, non un manuale. Alcuni consigli potrebbero sembrare un po 'contraddittori. Sta a te decidere quando è giusto applicare cosa. La guida sembra darci solo un percorso generale.

Spero che le tue domande abbiano avuto risposta per la tua soddisfazione. E ancora, complimenti per la domanda.

    
risposta data 19.10.2011 - 23:28
fonte
2

Se la mia memoria di .NET è ancora valida, un delegato è fondamentalmente una funzione ptr o functor. Aggiunge uno strato di riferimento indiretto a una chiamata di funzione in modo che le funzioni possano essere sostituite senza che il codice chiamante debba cambiare. Questa è la stessa cosa che fa un'interfaccia, eccetto che un'interfaccia raggruppa più funzioni insieme e un implementatore deve implementarle insieme.

Un modello di evento, in generale, è uno in cui qualcosa risponde agli eventi di elswhere (come i messaggi di Windows). L'insieme di eventi è solitamente aperto, possono venire in qualsiasi ordine e non sono necessariamente correlati tra loro. I delegati lavorano bene per questo in quanto ogni evento può chiamare una singola funzione senza bisogno di un riferimento a una serie di oggetti di implementazione che potrebbero contenere anche numerose funzioni irrilevanti. Inoltre (è qui che la mia memoria .NET è vaga), penso che più delegati possano essere collegati a un evento.

Il compositing, sebbene non abbia molta familiarità con il termine, è fondamentalmente la progettazione di un oggetto per avere più sotto-parti, o bambini aggregati a cui il lavoro viene tramandato. I delegati consentono ai bambini di essere mixati e abbinati in un modo più specifico in cui un'interfaccia potrebbe essere eccessiva o causare troppo accoppiamento e la rigidità e la fragilità che ne deriva.

Il vantaggio di un'interfaccia per i metodi correlati è che i metodi possono condividere lo stato dell'oggetto di implementazione. Le funzioni delegate non possono condividere in modo così semplice, o persino contenere, lo stato.

Se una classe ha bisogno di una singola implementazione, un'interfaccia è più adatta poiché all'interno di qualsiasi classe che implementa l'intera raccolta, può essere eseguita solo un'implementazione e si ottengono i vantaggi di una classe di implementazione (stato, incapsulamento ecc.). Se l'implementazione potrebbe cambiare a causa dello stato di runtime, i delegati funzionano meglio perché possono essere scambiati per altre implementazioni senza influenzare gli altri metodi. Ad esempio, se ci sono tre delegati che hanno due possibili implementazioni, occorrono otto diverse classi che implementano l'interfaccia a tre metodi per tenere conto di tutte le possibili combinazioni di stati.

    
risposta data 18.10.2011 - 00:40
fonte
1

Il modello di progettazione "eventing" (meglio noto come Pattern Observer) consente di allegare più metodi della stessa firma al delegato. Non puoi farlo con un'interfaccia.

Non sono per niente convinto che la composizione sia più facile per un delegato piuttosto che un'interfaccia. Questa è una dichiarazione molto strana. Mi chiedo se intenda perché è possibile allegare metodi anonimi a un delegato.

    
risposta data 13.10.2011 - 13:49
fonte
1

Il più grande chiarimento che posso offrire:

  1. Un delegato definisce una firma di funzione - quali parametri accetterà una funzione corrispondente e cosa restituirà.
  2. Un'interfaccia riguarda un intero insieme di funzioni, eventi, proprietà e campi.

Quindi:

  1. Quando vuoi generalizzare alcune funzioni con la stessa firma, usa un delegato.
  2. Quando vuoi generalizzare qualche comportamento o qualità di una classe - usa un'interfaccia.
risposta data 18.10.2011 - 23:40
fonte
1

La tua domanda sugli eventi è già stata ben trattata. Ed è anche vero che un'interfaccia può definire più metodi (ma in realtà non è necessario), mentre un tipo di funzione pone sempre vincoli su una singola funzione.

La vera differenza tuttavia è:

    I valori delle funzioni
  • sono abbinati ai tipi di funzione tramite sottotipizzazione strutturale (ossia compatibilità implicita con la struttura richiesta). Ciò significa che se un valore di funzione ha una firma compatibile con il tipo di funzione, è un valore valido per il tipo.
  • Le istanze
  • sono abbinate alle interfacce tramite sottotitoli nominali (ad esempio l'uso esplicito di un nome). Ciò significa che se un'istanza è membro di una classe, che implementa esplicitamente l'interfaccia specificata, l'istanza è un valore valido per l'interfaccia. Tuttavia, se l'oggetto ha solo tutti i membri richiesti dalle interfacce e tutti i membri hanno firme compatibili, ma non implementa esplicitamente l'interfaccia, non è un valore valido per l'interfaccia.

Certo, ma cosa significa?

Prendiamo questo esempio (il codice è in haXe dato che il mio C # non è molto buono):

class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:T->Bool):Collection<T> { 
         //build a new collection with all elements e, such that predicate(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

Ora il metodo filter rende facile passare in modo semplice una piccola parte di logica, che non è a conoscenza dell'organizzazione interna della collezione, mentre la collezione non dipende dalla logica assegnata ad essa. Grande. Tranne un problema:
La collezione fa dipende dalla logica. La raccolta implica intrinsecamente che la funzione passata sia progettata per testare un valore rispetto a una condizione e restituire il successo del test. Si noti che non tutte le funzioni, che accettano un valore come argomento e restituiscono un valore booleano, sono in realtà semplici predicati. Ad esempio il metodo di rimozione della nostra collezione è una funzione del genere.
Supponiamo di aver chiamato c.filter(c.remove) . Il risultato sarebbe una raccolta con tutti gli elementi di c mentre c stessa diventa vuota. Questo è un peccato, perché naturalmente ci aspetteremmo che c stesso sia invariato.

L'esempio è molto costruito. Ma il problema chiave è che il codice che chiama c.filter con un valore di funzione come argomento non ha modo di sapere se quell'argomento è adatto (cioè, alla fine, conserverà l'invariante). Il codice che ha creato il valore della funzione può o non può sapere, che sarà interpretato come un predicato.

Ora cambiamo le cose:

interface Predicate<T> {
    function test(value:T):Bool;
}
class Collection<T> {
    /* a lot of code we don't care about now */
    public function filter(predicate:Predicate<T>):Collection<T> { 
         //build a new collection with all elements e, such that predicate.test(e) == true
    }
    public function remove(e:T):Bool {
         //removes an element from this collection, if contained and returns true, false otherwise
    }
}

Cosa è cambiato? Ciò che è cambiato è che qualunque valore ora è dato a filter ha esplicitamente firmato il contratto di essere un predicato. Ovviamente i programmatori maliziosi o estremamente stupidi creano implementazioni dell'interfaccia che non sono prive di effetti collaterali e quindi non sono predicati.
Ma ciò che non può più accadere è che qualcuno leghi un'unità logica di dati / codice, che erroneamente viene interpretata come un predicato a causa della sua struttura esterna.

Quindi, per riformulare quanto detto sopra in poche parole:

  • interfacce indica l'uso della digitazione nominale e quindi delle relazioni esplicite
  • delegati significano l'uso della tipizzazione strutturale e quindi delle relazioni implicite

Il vantaggio delle relazioni esplicite è che puoi essere sicuro di loro. Lo svantaggio è che richiedono il sovraccarico di esplicitezza. Viceversa, lo svantaggio delle relazioni implicite (nel nostro caso la firma di una funzione che corrisponde alla firma desiderata) è che non si può essere sicuri al 100% che si possano usare le cose in questo modo. Il vantaggio è che puoi stabilire relazioni senza tutto il sovraccarico. Puoi semplicemente lanciare le cose velocemente, perché la loro struttura lo consente. Ecco cosa significa composizione semplice.
È un po 'come LEGO: puoi poter semplicemente collegare una figura LEGO di Star Wars su una nave pirata LEGO, semplicemente perché la struttura esterna lo consente. Ora potresti sentire che è terribilmente sbagliato, o potrebbe essere esattamente quello che vuoi. Nessuno ti fermerà.

    
risposta data 19.10.2011 - 19:17
fonte
1
  1. Un modello di progettazione evento implica una struttura che implica un meccanismo di pubblicazione e sottoscrizione. Gli eventi sono pubblicati da una fonte, ogni sottoscrittore ottiene la propria copia dell'elemento pubblicato ed è responsabile delle proprie azioni. Questo è un meccanismo liberamente accoppiato perché l'editore non ha nemmeno bisogno di sapere che gli abbonati ci sono. Per non parlare degli abbonati non è obbligatorio (non può esserlo)
  2. composizione è "più semplice" se viene utilizzato un delegato rispetto a un'interfaccia. Le interfacce definiscono un'istanza di classe come un oggetto "tipo di gestore" dove, come un'istanza di costruzione di composizione, sembra "avere un gestore", pertanto l'aspetto dell'istanza è meno limitato utilizzando un delegato e diventa più flessibile.
  3. Dal commento qui sotto ho scoperto che avevo bisogno di migliorare il mio post, vedere questo per riferimento: link
risposta data 20.10.2011 - 11:58
fonte

Leggi altre domande sui tag