Co rozumieją przez wzorzec projektowania eventing?
Najprawdopodobniej odnoszą się do implementacji wzorca obserwatora, który jest konstrukcją języka podstawowego w języku C #, ujawnioną jako „ zdarzenia ”. Odsłuchiwanie wydarzeń jest możliwe poprzez podpięcie do nich delegata. Jak zauważył Yam Marcovic, EventHandler
jest to konwencjonalny podstawowy typ delegata dla zdarzeń, ale można użyć dowolnego typu delegata.
Jak kompozycja okazuje się łatwa, jeśli używa się delegata?
Prawdopodobnie dotyczy to tylko elastyczności oferty delegatów. Możesz łatwo „skomponować” określone zachowanie. Przy pomocy lambdas składnia do tego jest również bardzo zwięzła. Rozważ następujący przykład.
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 );
Wykonanie czegoś podobnego z interfejsami wymagałoby albo użycia wzorca strategii, albo (abstrakcyjnej) Bunny
klasy bazowej, z której można rozszerzyć bardziej szczegółowe króliczki.
jeśli istnieje grupa powiązanych metod, które można wywołać, należy użyć interfejsu - jakie to ma korzyści?
Znowu użyję króliczków, aby pokazać, jak byłoby łatwiej.
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();
...
jeśli klasa potrzebuje tylko jednej implementacji metody, użyj interfejsu - jak jest to uzasadnione pod względem korzyści?
W tym celu ponownie rozważ pierwszy przykład z króliczkiem. Jeśli kiedykolwiek potrzebna jest tylko jedna implementacja - nie jest wymagana żadna kompozycja niestandardowa - możesz ujawnić to zachowanie jako interfejs. Nigdy nie będziesz musiał budować lambdas, możesz po prostu użyć interfejsu.
Wniosek
Delegaci oferują znacznie większą elastyczność, a interfejsy pomagają w zawarciu silnych umów. Dlatego znajduję ostatni wspomniany punkt: „Klasa może wymagać więcej niż jednej implementacji metody”. , zdecydowanie najbardziej odpowiedni.
Dodatkowym powodem używania delegatów jest to, że chcesz odsłonić tylko część klasy, z której nie można dostosować pliku źródłowego.
Jako przykład takiego scenariusza (maksymalna elastyczność, nie trzeba modyfikować źródeł), rozważ tę implementację algorytmu wyszukiwania binarnego dla każdego możliwego zbioru, przekazując tylko dwóch delegatów.
1) Wzory eventingowe, cóż, klasycznym jest wzorzec Observer, oto dobry link Microsoft Talk Observer
Artykuł, do którego linkujesz, nie jest zbyt dobrze napisany imo i sprawia, że sprawy są bardziej skomplikowane niż to konieczne. Statyczny biznes ma zdrowy rozsądek, nie można zdefiniować interfejsu z elementami statycznymi, więc jeśli chcesz polimorficznego zachowania w metodzie statycznej, użyłbyś delegata.
Powyższy link mówi o delegatach i dlaczego warto je komponować, aby pomóc w zapytaniu.
źródło
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
this
argument 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:
Kompilator faktycznie przekształca to w następujący kod:
Następnie subskrybujesz wydarzenie, robiąc to
Który kompiluje do
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:
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:
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.
źródło
Jeśli nadal mam pamięć .NET, delegat jest w zasadzie funkcją ptr lub funktorem. Dodaje warstwę pośrednią do wywołania funkcji, dzięki czemu funkcje można zastąpić bez konieczności zmiany kodu wywołującego. Jest to to samo, co interfejs, z wyjątkiem tego, że interfejs pakuje wiele funkcji razem, a implementator musi je zaimplementować razem.
Ogólnie rzecz biorąc, wzorzec zdarzeń to taki, w którym coś reaguje na zdarzenia z dowolnego miejsca (takie jak komunikaty systemu Windows). Zbiór wydarzeń jest zwykle otwarty, mogą występować w dowolnej kolejności i niekoniecznie są ze sobą powiązane. Delegaci pracują w tym dobrze, ponieważ każde zdarzenie może wywołać pojedynczą funkcję bez potrzeby odwoływania się do szeregu obiektów implementujących, które mogą również zawierać wiele nieistotnych funkcji. Ponadto (w tym przypadku moja pamięć .NET jest niejasna), myślę, że do wydarzenia można dołączyć wielu delegatów.
Komponowanie, choć nie znam tego pojęcia, w zasadzie polega na zaprojektowaniu jednego obiektu tak, aby zawierał wiele pod-części lub zagregowanych elementów potomnych, do których praca jest przekazywana. Delegaci pozwalają mieszać i dopasowywać dzieci w bardziej doraźny sposób, w którym interfejs może być przesadny lub powodować nadmierne sprzężenie oraz towarzyszącą mu sztywność i kruchość.
Zaletą interfejsu dla powiązanych metod jest to, że metody mogą współużytkować stan obiektu implementującego. Funkcje delegowania nie mogą tak wspaniale współdzielić ani nawet zawierać stanu.
Jeśli klasa wymaga pojedynczej implementacji, interfejs jest bardziej odpowiedni, ponieważ w dowolnej klasie implementującej całą kolekcję można wykonać tylko jedną implementację i uzyskać korzyści z klasy implementującej (stan, enkapsulacja itp.). Jeśli implementacja może ulec zmianie ze względu na stan środowiska wykonawczego, delegaci działają lepiej, ponieważ można je zamienić na inne implementacje bez wpływu na inne metody. Na przykład, jeśli są trzy delegaty, z których każdy ma dwie możliwe implementacje, potrzebujesz ośmiu różnych klas implementujących interfejs trzech metod, aby uwzględnić wszystkie możliwe kombinacje stanów.
źródło
Wzorzec projektu „eventing” (lepiej znany jako wzorzec obserwatora) umożliwia dołączenie do delegata wielu metod tego samego podpisu. Naprawdę nie można tego zrobić za pomocą interfejsu.
Jednak wcale nie jestem przekonany, że składanie jest łatwiejsze dla delegata niż interfejs. To bardzo dziwne stwierdzenie. Zastanawiam się, czy ma na myśli, ponieważ możesz dołączyć anonimowe metody do delegata.
źródło
Największe wyjaśnienie, jakie mogę zaoferować:
Więc:
źródło
Twoje pytanie dotyczące wydarzeń zostało już dobrze ujęte. Prawdą jest również to, że interfejs może definiować wiele metod (ale tak naprawdę nie musi), podczas gdy typ funkcji zawsze nakłada ograniczenia na pojedynczą funkcję.
Prawdziwa różnica polega jednak na:
Jasne, ale co to znaczy?
Weźmy ten przykład (kod jest w haXe, ponieważ mój C # nie jest zbyt dobry):
Teraz metoda filtrowania ułatwia wygodne przekazywanie tylko niewielkiej logiki, która nie jest świadoma wewnętrznej organizacji kolekcji, podczas gdy kolekcja nie zależy od nadanej jej logiki. Świetny. Z wyjątkiem jednego problemu:
Zbiór nie zależą od logiki. Kolekcja nieodłącznie przyjmuje założenie, że przekazana funkcja ma na celu przetestowanie wartości względem warunku i zwrócenie sukcesu testu. Zauważ, że nie wszystkie funkcje, które przyjmują jedną wartość jako argument i zwracają wartość logiczną, są w rzeczywistości zwykłymi predykatami. Na przykład metoda usuwania naszej kolekcji jest taką funkcją.
Załóżmy, że zadzwoniliśmy
c.filter(c.remove)
. Rezultatem byłaby kolekcja ze wszystkimi elementamic
whilec
sam staje się pusty. Jest to niefortunne, ponieważ naturalnie spodziewalibyśmyc
się, że będzie niezmienny.Przykład jest bardzo skonstruowany. Ale kluczowym problemem jest to, że wywołanie kodu
c.filter
z pewną wartością funkcji jako argumentu nie ma możliwości dowiedzenia się, czy ten argument jest odpowiedni (tj. Ostatecznie zachowa niezmiennik). Kod, który utworzył wartość funkcji może, ale nie musi wiedzieć, że będzie interpretowany jako predykat.Teraz zmieńmy rzeczy:
Co się zmieniło? To, co się zmieniło, polega na tym, że niezależnie od tego, jaką wartość się teraz otrzymuje
filter
, wyraźnie podpisano umowę o predykacie. Oczywiście złośliwi lub wyjątkowo głupi programiści tworzą implementacje interfejsu, które nie są wolne od skutków ubocznych, a zatem nie są predykatami.Ale to, czego już nie może się wydarzyć, to to, że ktoś wiąże logiczną jednostkę danych / kodu, która błędnie zostaje zinterpretowana jako predykat ze względu na jego zewnętrzną strukturę.
Aby sformułować to, co zostało powiedziane powyżej, w kilku słowach:
Zaletą wyraźnych relacji jest to, że możesz być ich pewien. Wadą jest to, że wymagają narzutu jawności. I odwrotnie, wadą ukrytych relacji (w naszym przypadku sygnatura jednej funkcji pasująca do pożądanej sygnatury) jest to, że tak naprawdę nie możesz być w 100% pewien, że możesz używać tego w ten sposób. Zaletą jest to, że możesz nawiązywać relacje bez wszystkich kosztów ogólnych. Możesz po prostu szybko poskładać rzeczy, ponieważ pozwala na to ich struktura. To właśnie oznacza łatwa kompozycja.
To trochę jak LEGO: Państwo może po prostu wtyczką LEGO Star Wars na postać z LEGO statek piracki, po prostu dlatego, że struktura zewnętrzna na to pozwala. Teraz możesz poczuć, że jest to okropnie złe lub może być dokładnie tym, czego chcesz. Nikt cię nie powstrzyma.
źródło
int IList.Count { get { ... } }
) Lub niejawnie (public int Count { get { ... } }
). To rozróżnienie nie ma znaczenia w tej dyskusji, ale zasługuje na wzmiankę, aby uniknąć mylących czytelników.źródło