Najlepsze rozwiązania dotyczące wzorca projektowego obserwatora

Na platformie .NET wzorzec projektowy obserwatora jest implementowany jako zestaw interfejsów. Interfejs System.IObservable<T> reprezentuje dostawcę danych, który jest również odpowiedzialny za zapewnienie IDisposable implementacji, która umożliwia obserwatorom anulowanie subskrypcji powiadomień. Interfejs System.IObserver<T> reprezentuje obserwatora.

W tym artykule opisano najlepsze rozwiązania, które należy zastosować podczas implementowania wzorca projektowego obserwatora za pomocą tych interfejsów.

Rozważ alternatywy przed wdrożeniem

Interfejsy IObservable<T> i IObserver<T> są odpowiednie dla scenariuszy powiadomień opartych na wypychaniach, ale inne wzorce .NET mogą być lepiej dopasowane. Aby uzyskać proste powiadomienie w ramach jednej aplikacji, użyj zdarzeń. W przypadku asynchronicznych sekwencji typu pull, w których konsument kontroluje tempo, użyj IAsyncEnumerable<T>. W przypadku wzorca producent–konsument z mechanizmem kontroli ciśnienia zwrotnego użyj System.Threading.Channels. W przypadku złożonego tworzenia, filtrowania i przekształcania zdarzeń należy użyć pakietu System.Reactive (Rx.NET) zamiast implementować bezpośrednio IObservable<T>. Aby uzyskać więcej informacji, zobacz Wzorzec projektowania obserwatora.

Używanie oddzielnego typu dla danych powiadomień

Obiekt zawierający dane, które dostawca wysyła swoim obserwatorom, odpowiada parametrowi typu generycznego elementów IObservable<T> i IObserver<T>. Chociaż ten obiekt może być taki sam jak implementacja IObservable<T> , zdefiniuj go jako oddzielny typ. Dedykowany typ danych zachowuje obowiązki dostawcy niezależnie od ładunku powiadomień i ułatwia rozwijanie interfejsu API.

Nie należy polegać na kolejności powiadomień

Kolejność odbierania powiadomień przez obserwatorów nie jest zdefiniowana. Dostawca może użyć dowolnej metody w celu określenia kolejności, więc nie zapisuj obserwatorów, którzy zależą od powiadamiania przed lub po innym obserwatorze.

Zapewnij, aby Subscribe i Dispose były bezpieczne dla wątków

Zazwyczaj dostawca implementuje IObservable<T>.Subscribe metodę przez dodanie obserwatora do listy subskrybentów reprezentowanej przez obiekt kolekcji i implementuje IDisposable.Dispose metodę przez usunięcie obserwatora z tej listy. Obserwator może wywołać te metody w dowolnym momencie. Umowa dostawcy/obserwatora nie określa, kto jest odpowiedzialny za anulowanie subskrypcji po IObserver<T>.OnCompleted metodzie wywołania zwrotnego, więc dostawca i obserwator mogą próbować usunąć tego samego członka z listy.

Aby uniknąć warunków wyścigu, zarówno metody, jak Subscribe i Dispose są bezpieczne wątkowo. Zazwyczaj wiąże się to z użyciem kolekcji współbieżnej lub blokady. Implementacje, które nie są bezpieczne wątkowo, powinny jawnie udokumentować, że nie są.

Dokumentowanie wszelkich dodatkowych gwarancji kontraktowych

Określ wszelkie dodatkowe gwarancje w warstwie nad kontraktem dostawca/obserwator. Jeśli nakładasz dodatkowe wymagania, wyraźnie to zaznacz, aby użytkownicy nie byli zdezorientowani co do kontraktu obserwatora.

Traktuj wyjątki jako informacje

Ze względu na luźne sprzężenie między dostawcą danych a obserwatorem wyjątki we wzorcu projektowania obserwatora mają być informacyjne. Ta cecha wpływa na sposób obsługi wyjątków przez dostawców i obserwatorów.

Wywołaj funkcję OnError tylko wtedy, gdy aktualizacje nie mogą kontynuować

Metoda OnError jest przeznaczona jako komunikat informacyjny dla obserwatorów, podobnie jak IObserver<T>.OnNext metoda. OnNext Jednak metoda zapewnia obserwatorowi bieżące lub zaktualizowane dane, natomiast OnError metoda wskazuje, że dostawca nie może dostarczyć prawidłowych danych.

Postępuj zgodnie z poniższymi najlepszymi rozwiązaniami podczas obsługi wyjątków i wywoływania OnError metody:

  • Dostawca musi obsługiwać własne wyjątki, jeśli ma określone wymagania.
  • Dostawca nie powinien oczekiwać ani wymagać, aby obserwatorzy obsługiwali wyjątki w żaden konkretny sposób.
  • Dostawca powinien wywołać metodę OnError , gdy obsługuje wyjątek, który narusza jego zdolność do dostarczania aktualizacji. Przekazywanie informacji o takich wyjątkach do obserwatora. W innych przypadkach nie ma potrzeby powiadamiania obserwatorów o wyjątku.

Po wywołaniu przez dostawcę metody OnError lub IObserver<T>.OnCompleted nie powinno już być żadnych dalszych powiadomień, a dostawca może wyrejestrować swoich obserwatorów. Obserwatorzy mogą jednak w dowolnym momencie anulować subskrypcję, w tym przed i po otrzymaniu powiadomieniaOnError.IObserver<T>.OnCompleted Wzorzec projektu obserwatora nie określa, czy dostawca lub obserwator jest odpowiedzialny za anulowanie subskrypcji, więc obaj mogą próbować anulować subskrypcję. Zazwyczaj, gdy obserwatorzy wypisują się z subskrypcji, są usuwani z kolekcji subskrybentów. W aplikacji jednowątkowej implementacja IDisposable.Dispose powinna upewnić się, że odwołanie do obiektu jest prawidłowe i że obiekt jest członkiem kolekcji subskrybentów przed podjęciem próby usunięcia obiektu. W aplikacji wielowątkowej użyj blokady, aby chronić kolekcję obserwatorów.

Uznawaj powiadomienia OnError za informacyjne w obserwatorach

Gdy obserwator otrzymuje powiadomienie o błędzie od dostawcy, obserwator powinien traktować wyjątek jako informacyjny i nie powinien być wymagany do podjęcia żadnej konkretnej akcji.

Postępuj zgodnie z tymi najlepszymi praktykami, gdy odpowiadasz na wywołanie metody OnError od dostawcy:

  • Nie zgłaszaj wyjątków z implementacji interfejsu, takich jak OnNext lub OnError. Jeśli obserwator zgłasza wyjątki, należy oczekiwać, że te wyjątki nie będą obsługiwane.
  • Aby zachować stos wywołań, obserwator, który chce zgłosić obiekt Exception przekazany do swojej metody OnError, powinien opakować wyjątek przed jego ponownym zgłoszeniem. W tym celu należy użyć standardowego obiektu wyjątku.

Nie wyrejestrowuj w metodzie subskrybowania

Nie próbuj wyrejestrowywać w metodzie IObservable<T>.Subscribe, ponieważ może to spowodować puste odwołanie.

Dołączanie obserwatora do jednego dostawcy

Chociaż obserwatora można przypisać do wielu dostawców, zaleca się przypisywanie jednego wystąpienia IObserver<T> tylko do jednego wystąpienia IObservable<T>.