Programowanie oparte na zdarzeniach: kiedy warto?

19

Ok, wiem, że tytuł tego pytania jest prawie identyczny jak Kiedy powinienem używać programowania opartego na zdarzeniach? ale odpowiedzi na to pytanie nie pomogły mi zdecydować, czy powinienem wykorzystać wydarzenia w konkretnym przypadku, w którym się znajduję.

Tworzę małą aplikację. Jest to prosta aplikacja i w większości jej funkcjonalność jest podstawową CRUD.

Po określonych zdarzeniach (podczas modyfikacji niektórych danych) aplikacja musi zapisać lokalną kopię tych danych w pliku. Nie jestem pewien, jaki jest najlepszy sposób na wdrożenie tego. Mogę:

  • Uruchom zdarzenia, gdy dane zostaną zmodyfikowane i powiąż odpowiedź (wygeneruj plik) z takimi zdarzeniami. Alternatywnie, zaimplementuj wzorzec obserwatora. To wydaje się niepotrzebną złożonością.
  • Wywołaj kod generujący plik bezpośrednio z kodu, który modyfikuje dane. O wiele prostsze, ale wydaje się błędne, że zależność powinna być w ten sposób, to znaczy wydaje się błędne, że podstawowa funkcjonalność aplikacji (kod modyfikujący dane) powinna być połączona z tym dodatkowym profitem (kod, który generuje plik kopii zapasowej). Wiem jednak, że ta aplikacja nie rozwinie się do punktu, w którym to połączenie stanowi problem.

Jakie jest najlepsze podejście w tym przypadku?

abl
źródło
2
Powiedziałbym, że nie wdrażaj niczego samodzielnie - po prostu użyj istniejącej magistrali zdarzeń. Ułatwi to życie ...
Boris the Spider
Programowanie sterowane zdarzeniami jest z natury asynchroniczne. Zdarzenia mogą, ale nie muszą, przychodzić w kolejności, w jakiej zamierzałeś, a może w innej kolejności lub wcale. Jeśli możesz poradzić sobie z tą dodatkową złożonością, idź po nią.
Pieter B
Sterowane zdarzeniami zazwyczaj oznaczają, że kod jest dostarczany jako oddzwanianie i są one wywoływane z dowolnego miejsca w sposób niemożliwy do przewidzenia. Twój opis brzmi bardziej tak, gdy coś szczególnego dzieje się w kodzie , musisz zrobić więcej niż naiwna implementacja. Wystarczy zakodować dodatkowe połączenia.
Thorbjørn Ravn Andersen
Jest to różnica między zdarzeniami i opartego na zdarzeniu. Zobacz na przykład prowokujący do myślenia odcinek 35. podcastu .NET Rocks z Tedem Faisonem, Ted Faison podnosi wydarzenia na maksa! ( bezpośredni adres URL pobierania ) i książki Programowanie oparte na zdarzeniach: maksymalne zwiększenie liczby zdarzeń .
Peter Mortensen
Wywiad z Tedem Faisonem rozpoczyna się o 13 min. 10 sek.
Peter Mortensen

Odpowiedzi:

31

Postępuj zgodnie z zasadą KISS: zachowaj prostotę, głupotę lub zasadę YAGNI: nie będziesz jej potrzebować.

Możesz napisać kod taki jak:

void updateSpecialData() {
    // do the update.
    backupData();
}

Lub możesz napisać kod taki jak:

void updateSpecialData() {
     // do the update.
     emit SpecialDataUpdated();
}

void SpecialDataUpdatedHandler() {
     backupData();
}

void configureEventHandlers() {
     connect(SpecialDataUpdate, SpecialDataUpdatedHandler);
}

W przypadku braku istotnego powodu, aby zrobić inaczej, wybierz prostszą drogę. Techniki takie jak obsługa zdarzeń są potężne, ale zwiększają złożoność kodu. Do działania wymaga więcej kodu i utrudnia śledzenie tego, co dzieje się w kodzie.

Zdarzenia są bardzo krytyczne w odpowiedniej sytuacji (wyobraź sobie próbę programowania interfejsu użytkownika bez zdarzeń!) Ale nie używaj ich, gdy możesz zamiast tego KISS lub YAGNI.

Winston Ewert
źródło
Szczególnie podoba mi się fakt, że wspomniałeś, że wyzwalanie zdarzeń przy zmianie danych nie jest trywialne.
NoChance,
13

Przykład opisujący proste dane, w którym modyfikacja wywołuje pewien efekt, można doskonale zaimplementować za pomocą wzorca projektowego obserwatora :

  • jest to łatwiejsze do wdrożenia i utrzymania niż kod sterowany pełnym zdarzeniem.
  • sprzężenie między podmiotem a obserwatorem może być abstrakcyjne, co ułatwia rozdzielenie obaw.
  • jest idealny dla relacji jeden do wielu (badani mają jednego lub wielu obserwatorów).

Podejście oparte na zdarzeniach jest warte inwestycji w bardziej złożone scenariusze, w których może wystąpić wiele różnych interakcji, w kontekście wielu do wielu, lub jeśli przewiduje się reakcje łańcuchowe (np. Pacjent informuje obserwatora, który w niektórych przypadkach chce zmodyfikować temat lub inne przedmioty)

Christophe
źródło
1
Jestem zdezorientowany, czy obserwator nie jest tylko jednym ze sposobów realizacji wydarzeń?
svick
1
@svick Nie sądzę. W programowaniu sterowanym zdarzeniami masz główną pętlę, która przetwarza zdarzenia w relacji wiele do wielu, z oddzielonymi nadawcami i obserwatorami. Myślę, że obserwator może przyczynić się do przetworzenia zdarzenia typu peryferyjnego, ale nie można osiągnąć pełnego spektrum EDP tylko za pomocą obserwatora. Myślę, że zamieszanie wynika z faktu, że w oprogramowaniu sterowanym zdarzeniami obserwatorzy są czasami implementowani na szczycie przetwarzania zdarzeń (zazwyczaj MVC z GUI)
Christophe
5

Jak mówisz, wydarzenia są doskonałym narzędziem do zmniejszania sprzężenia między klasami; więc chociaż może wymagać pisania dodatkowego kodu w niektórych językach bez wbudowanej obsługi zdarzeń, zmniejsza złożoność dużego obrazu.

Zdarzenia są prawdopodobnie jednym z najważniejszych narzędzi w OO (według Alana Kay - Obiekty komunikują się poprzez wysyłanie i odbieranie wiadomości ). Jeśli używasz języka, który ma wbudowane wsparcie dla wydarzeń lub traktuje funkcje jak pierwszorzędni obywatele, korzystanie z nich nie stanowi problemu.

Nawet w językach bez wbudowanego wsparcia ilość płyty kotłowej dla czegoś takiego jak wzór Observer jest dość minimalna. Możesz znaleźć gdzieś przyzwoitą bibliotekę zdarzeń ogólnych, której możesz użyć we wszystkich swoich aplikacjach, aby zminimalizować płytę kotłową. (Ogólny agregator zdarzeń lub mediator zdarzeń jest przydatny w prawie każdym rodzaju aplikacji).

Czy warto w małej aplikacji? Powiedziałbym zdecydowanie tak .

  • Utrzymywanie oddzielonych klas od siebie powoduje, że wykres zależności klasowych jest czysty.
  • Klasy bez konkretnych zależności mogą być testowane oddzielnie bez uwzględnienia innych klas w testach.
  • Klasy bez konkretnych zależności wymagają mniej testów jednostkowych dla pełnego pokrycia.

Jeśli myślisz „Och, ale to naprawdę bardzo mała aplikacja, to tak naprawdę nie ma tak wielkiego znaczenia” , zastanów się:

  • Małe aplikacje czasem kończą się później z większymi aplikacjami.
  • Małe aplikacje prawdopodobnie zawierają przynajmniej pewną logikę lub komponenty, które mogą później wymagać ponownego wykorzystania w innych aplikacjach.
  • Wymagania dotyczące małych aplikacji mogą ulec zmianie, co powoduje konieczność refaktoryzacji, co jest łatwiejsze, gdy istniejący kod jest oddzielony.
  • Dodatkowe funkcje można dodać później, powodując potrzebę rozszerzenia istniejącego kodu, co jest również znacznie łatwiejsze, gdy istniejący kod jest już oddzielony.
  • Kod luźno powiązany zwykle nie zajmuje dużo więcej czasu niż kod ściśle sprzężony; ale kod ściśle powiązany zajmuje znacznie więcej czasu na refaktoryzację i testowanie niż kod luźno powiązany.

Ogólnie rzecz biorąc, rozmiar aplikacji nie powinien decydować o tym, czy klasy powinny być luźno powiązane; Zasady SOLID dotyczą nie tylko dużych aplikacji, ale mają zastosowanie do oprogramowania i baz kodów w dowolnej skali.

W rzeczywistości czas zaoszczędzony na testowaniu jednostek luźno powiązanych klas w oderwaniu powinien równoważyć każdy dodatkowy czas spędzony na oddzieleniu tych klas.

Ben Cottrell
źródło
2

Wzorzec obserwatora można zaimplementować w znacznie mniejszy sposób niż opisuje go artykuł w Wikipedii (lub książka GOF), zakładając, że języki programowania obsługują coś w rodzaju „oddzwaniania” lub „delegatów”. Po prostu przekaż metodę zwrotną do swojego kodu CRUD (metoda obserwatora, która może być albo ogólną metodą „zapisu do pliku”, albo pustą). Zamiast „odpalenia zdarzenia” wystarczy wywołać to wywołanie zwrotne.

Wynikowy kod będzie tylko minimalnie bardziej skomplikowany niż bezpośrednie wywołanie kodu generującego plik, ale bez wad ścisłego łączenia niepowiązanych ze sobą komponentów.

To przyniesie ci „najlepsze z obu światów”, bez poświęcania oddzielenia dla „YAGNI”.

Doktor Brown
źródło
Działa to za pierwszym razem Doc, ale gdy zdarzenie musi wywołać drugą rzecz, prawdopodobnie klasa będzie musiała zostać zmodyfikowana w ten sposób. Jeśli użyjesz wzorca obserwatora, możesz dodać tyle nowych zachowań, ile chcesz, bez otwierania oryginalnej klasy kopii zapasowej w celu modyfikacji.
RubberDuck,
2
@RubberDuck: OP szukał rozwiązania „unikającego niepotrzebnej złożoności” - jeśli potrzebne są różne zdarzenia / różne zachowania, prawdopodobnie nie uznałby wzorca obserwatora za zbyt skomplikowany. Zgadzam się więc z tym, co powiedziałeś, kiedy sprawy staną się bardziej złożone, pełny obserwator lepiej mu służy, ale tylko wtedy.
Doc Brown
Uczciwe oświadczenie, ale wydaje mi się, że jest to wybite okno.
RubberDuck,
2
@RubberDuck: dodanie pełnego obserwatora z mechaniką wydawcy / subskrybenta „na wszelki wypadek”, kiedy nie jest tak naprawdę potrzebne, wydaje mi się, że jest to nadinżynieria - to nie jest lepsze.
Doc Brown
1
Nie zgadzam się, że może to być zbytnie inżynieria. Prawdopodobnie czuję się tak, jak to robię, ponieważ implementacja w moim stosie jest trywialna. W każdym razie, po prostu zgodzimy się nie zgodzić?
RubberDuck,