Prima di tutto, bella domanda. Ammiro la tua attenzione all'utilità piuttosto che accettare ciecamente le "migliori pratiche". +1 per quello.
Ho letto quella guida prima. Devi ricordare qualcosa al riguardo: è solo una guida, principalmente per i nuovi arrivati di C # che sanno programmare ma non hanno molta familiarità con il modo di fare C #. Non è tanto una pagina di regole quanto una pagina che descrive come le cose sono già fatte di solito. E dato che sono già stati fatti così ovunque, potrebbe essere una buona idea rimanere coerenti.
Arriverò al punto, rispondendo alle tue domande.
Prima di tutto, suppongo che tu sappia già cos'è un'interfaccia. Per quanto riguarda un delegato, è sufficiente dire che è una struttura che contiene un puntatore tipizzato a un metodo, insieme a un puntatore facoltativo all'oggetto che rappresenta l' this
argomento per quel metodo. Nel caso di metodi statici, quest'ultimo puntatore è nullo.
Esistono anche delegati multicast, che sono esattamente come i delegati, ma possono avere a loro assegnate diverse di queste strutture (il che significa che una singola chiamata a Invocare su un delegato multicast richiama tutti i metodi nell'elenco di invocazione assegnato).
Cosa significano per un modello di progettazione di eventi?
Intendono usare eventi in C # (che ha parole chiave speciali per implementare sofisticamente questo modello estremamente utile). Gli eventi in C # sono alimentati da delegati multicast.
Quando si definisce 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à lo trasforma 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
}
}
Ti iscrivi quindi a un evento facendo
MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;
Che si compila fino a
MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));
Quindi questo è un evento in C # (o .NET in generale).
In che modo la composizione risulta semplice se viene utilizzato un delegato?
Questo può essere facilmente dimostrato:
Supponiamo di avere una classe che dipende da una serie di azioni da passare ad essa. È possibile incapsulare tali azioni in un'interfaccia:
interface RequiredMethods {
void DoX();
int DoY();
};
E chiunque volesse passare azioni alla tua classe dovrebbe prima implementare quell'interfaccia. Oppure potresti rendere le loro vite più facili a seconda della 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 legare i metodi ai delegati in fase di esecuzione. Di solito è più facile.
Questo modo di fare le cose è estremamente utile nelle giuste circostanze. Pensaci: perché dipendere da un'interfaccia quando tutto ciò che ti interessa davvero è avere un'implementazione passata a te?
Vantaggi dell'uso delle interfacce in presenza di un gruppo di metodi correlati
È utile utilizzare le interfacce perché le interfacce normalmente richiedono implementazioni esplicite in fase di compilazione. Ciò significa che crei una nuova classe.
E se hai un gruppo di metodi correlati in un singolo pacchetto, è utile che quel pacchetto sia riutilizzabile da altre parti del codice. Quindi, se possono semplicemente creare un'istanza di una classe invece di creare un insieme di delegati, è più facile.
Vantaggi dell'uso delle interfacce se una classe necessita solo di un'implementazione
Come notato in precedenza, le interfacce vengono implementate in fase di compilazione, il che significa che sono più efficienti di invocare un delegato (che è un livello di indiretta di per sé).
"Un'implementazione" potrebbe significare un'implementazione che esiste un unico luogo ben definito.
In caso contrario, un'implementazione potrebbe venire da qualsiasi parte del programma, che sembra appena essere conformi al metodo di firma. Ciò consente una maggiore flessibilità, poiché i metodi devono solo conformarsi alla firma prevista, anziché appartenere a una classe che implementa esplicitamente un'interfaccia specifica. Ma quella flessibilità potrebbe avere un costo, e in realtà rompe il principio di sostituzione di Liskov , perché la maggior parte delle volte si desidera esplicitare, perché riduce al minimo la possibilità di incidenti. Proprio come la digitazione statica.
Il termine potrebbe anche riferirsi ai delegati multicast qui. I metodi dichiarati dalle interfacce possono essere implementati una sola volta in una classe di implementazione. Ma i delegati possono accumulare più metodi, che verranno chiamati in sequenza.
Quindi, tutto sommato, sembra che la guida non sia abbastanza istruttiva e funzioni semplicemente come è: una guida, non un libro delle regole. Alcuni consigli potrebbero effettivamente 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 ricevuto risposta in modo soddisfacente. E ancora, complimenti per la domanda.