Przede wszystkim dobre pytanie. Podziwiam twój nacisk na użyteczność, a nie ślepe przyjmowanie „najlepszych praktyk”. +1 za to.
Przeczytałem już ten przewodnik. Musisz coś o tym pamiętać - to tylko przewodnik, głównie dla początkujących C #, którzy wiedzą, jak programować, ale nie są tak obeznani z robieniem rzeczy w C #. Jest to nie tyle strona reguł, ile strona, która opisuje, w jaki sposób rzeczy są już zwykle wykonywane. A ponieważ są już wszędzie tak robione, dobrym pomysłem może być zachowanie spójności.
Przejdę do sedna, odpowiadając na twoje pytania.
Po pierwsze zakładam, że wiesz już, czym jest interfejs. Jeśli chodzi o delegata, wystarczy powiedzieć, że jest to struktura zawierająca typowany wskaźnik do metody wraz z opcjonalnym wskaźnikiem do obiektu reprezentującego thisargument dla tej metody. W przypadku metod statycznych ten ostatni wskaźnik jest zerowy.
Istnieją również delegaci multiemisji, którzy są tak samo jak delegaci, ale mogą mieć przypisane kilka z tych struktur (co oznacza pojedyncze wywołanie Invoke na delegacie multiemisji wywołuje wszystkie metody z przypisanej listy wywołań).
Co rozumieją przez wzorzec projektowania eventing?
Oznaczają one używanie zdarzeń w języku C # (który zawiera specjalne słowa kluczowe do zaawansowanego wdrażania tego niezwykle użytecznego wzorca). Wydarzenia w C # są napędzane przez delegatów multiemisji.
Podczas definiowania zdarzenia, takiego jak w tym przykładzie:
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
}
}
Kompilator faktycznie przekształca to w następujący kod:
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
}
}
Następnie subskrybujesz wydarzenie, robiąc to
MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;
Który kompiluje do
MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));
To się dzieje w C # (lub .NET w ogóle).
Jak kompozycja okazuje się łatwa, jeśli używa się delegata?
Można to łatwo wykazać:
Załóżmy, że masz klasę, która zależy od zestawu działań, które zostaną do niej przekazane. Możesz zamknąć te działania w interfejsie:
interface RequiredMethods {
void DoX();
int DoY();
};
I każdy, kto chciałby przekazać działania twojej klasie, musiałby najpierw wdrożyć ten interfejs. Lub możesz ułatwić ich życie , zależnie od następujących klas:
sealed class RequiredMethods {
public Action DoX;
public Func<int> DoY();
}
W ten sposób osoby dzwoniące muszą jedynie utworzyć instancję RequiredMethods i powiązać metody z delegatami w czasie wykonywania. To jest zwykle łatwiejsze.
Ten sposób robienia rzeczy jest niezwykle korzystny w odpowiednich okolicznościach. Pomyśl o tym - po co polegać na interfejsie, skoro naprawdę zależy Ci na tym, aby przekazać Ci implementację?
Korzyści z używania interfejsów, gdy istnieje grupa powiązanych metod
Korzystne jest używanie interfejsów, ponieważ interfejsy zwykle wymagają wyraźnych implementacji czasu kompilacji. Oznacza to, że tworzysz nową klasę.
A jeśli masz grupę powiązanych metod w jednym pakiecie, korzystne jest, aby ten pakiet mógł być ponownie wykorzystany przez inne części kodu. Jeśli więc mogą po prostu utworzyć instancję klasy zamiast budować zestaw delegatów, jest to łatwiejsze.
Korzyści z używania interfejsów, jeśli klasa wymaga tylko jednej implementacji
Jak wspomniano wcześniej, interfejsy są wdrażane w czasie kompilacji - co oznacza, że są one bardziej wydajne niż wywoływanie delegata (co jest poziomem pośrednim per se).
„Jedna implementacja” może oznaczać implementację, która istnieje w jednym dobrze zdefiniowanym miejscu.
W przeciwnym razie implementacja może pochodzić z dowolnego miejsca w programie, co akurat jest zgodne z podpisem metody. Pozwala to na większą elastyczność, ponieważ metody muszą jedynie być zgodne z oczekiwanym podpisem, a nie należeć do klasy, która wyraźnie implementuje określony interfejs. Ale ta elastyczność może kosztować i faktycznie łamie zasadę substytucji Liskowa , ponieważ przez większość czasu potrzebujesz jawności, ponieważ minimalizuje ona ryzyko wypadków. Podobnie jak pisanie statyczne.
Termin może również odnosić się tutaj do delegatów multiemisji. Metody zadeklarowane przez interfejsy mogą być zaimplementowane tylko raz w klasie implementującej. Ale uczestnicy mogą gromadzić wiele metod, które będą wywoływane sekwencyjnie.
Podsumowując, wygląda na to, że przewodnik nie jest wystarczająco informacyjny i po prostu funkcjonuje tak, jak jest - przewodnikiem, a nie zbiorem reguł. Niektóre porady mogą brzmieć nieco sprzecznie. To Ty decydujesz, kiedy należy zastosować. Przewodnik wydaje nam się jedynie ogólną ścieżką.
Mam nadzieję, że odpowiedziano na twoje pytania. I znowu, podziękowania za pytanie.