IObservableWzór.12 Mówiąc o wzorcu projektowym Observer warto także wspomnieć o tym, że w najnowszym .NET-cię mamy dwa ciekawe interfejsy generyczne : IObserver<T> i IObservable<T>. Te dwa interfejsy miały swoją premierę z biblioteką Reactive Extensions (Rx)

Ich celem było obsłużyć strumień reaktywnych informacji. 

Zobaczmy co interfejs IObservable<T> w ogóle robi.  Jaki jest on mechanizm? Do podpinania się do zdarzeń używamy operatora += . W tym interfejsie będziemy używać metody Subscribe() . Ta metoda bierze za parametr tylko interfejs IObserver<T>

Wszystko to jest interfejsem więc w przeciwieństwie do delegat i zdarzeń nie mamy tutaj określonego mechanizmu działania. Możemy z tymi interfejsami zrobić co chcemy

A co z odpinanie się od obserwacji, czyli od subskrypcją. Idea ta jest wpierana przez interfejsy Metoda Subscribe() zwraca obiekt interfejsu  IDisposable. Będzie to token, który metodę Dispose() odłączy się obserwacji obiektu observable.

 

A co z interfejsem IObserver<T>? Został on stworzony z myślą o push-owaniu notyfikacji i dlatego ma on te 3 metody:

  • OnNext(T) : Zostanie on wywołany , gdy nowe zdarzenie zajdzie
  • OnCompleted() : wywołany, gdy źródła już nie ma więcej informacji do przekazania
  • OnError() jest on wywołany, gdy obserwator napotka błąd

Jest to tylko interfejs więc obsługa go zależy już tylko od Ciebie?  Możesz w ogóle zignorować metody OnCompleted() i OnError()

Stwórzmy więc prosty przykład obsługi obserwacji użytkownika, który wykona operację wejścia na stronę internetowa.

Najpierw musimy stworzyć token, który będzie naszym biletem do wycofywania się obserwacji od danego użytkownika.

private class SubscriptionToken : IDisposable
{
    private User user;
    public IObserver<Event> Observer;
    public SubscriptionToken(User user, IObserver<Event> observer)
    {
        this.user = user;
        Observer = observer;
    }
    public void Dispose()
    {
        user.subscriptions.Remove(this);
    }
}

Teraz czym będzie T w tym przykładzie. Tym razem nie jesteśmy zmuszeni dziedziczyć po EventArgs. Dlatego stwórzmy swój własny typ zdarzenia.

public class Event
{

}

public class GoinToWebSiteEvent : Event
{
    public string WebAdress;
}

Użytkownik, będzie generatorem tych zdarzeń . Jak widzisz użytkownik implementuje interfejs IObservable<Event> i ma metodę Subscribe gdzie on przyjmie obiekt obserwatora.

public class User : IObservable<Event>
{
    private readonly HashSet<SubscriptionToken> subscriptions
    = new HashSet<SubscriptionToken>();

    public IDisposable Subscribe(IObserver<Event> observer)
    {
        var subscription = new SubscriptionToken(this, observer);
        subscriptions.Add(subscription);
        return subscription;
    }

    public void GoToGoogle()
    {
        foreach (var sub in subscriptions)
        {
            sub.Observer.OnNext(new GoinToWebSiteEvent
            {
                WebAdress = "www.google.pl"
            });
        }
    }

    private class SubscriptionToken : IDisposable
    {
        private User user;
        public IObserver<Event> Observer;
        public SubscriptionToken(User user, IObserver<Event> observer)
        {
            this.user = user;
            Observer = observer;
        }
        public void Dispose()
        {
            user.subscriptions.Remove(this);
        }
    }
}

Jest to bardziej złożony proces niż stworzenie swojego zdarzenia tak jak to zrobiliśmy w poprzednim przykładzie. Mamy jednak wolności działania. Możesz np. ograniczyć możliwość subskrypcji, jeżeli dany obiekt próbuje do niego się podpiąć więcej niż raz.

To jednak nie koniec naszego przykładu. W końcu ktoś musi obserwować tego użytkownika. Musimy mieć klasę, która zaimplementuję IObserver<Event>

public class UserHistoryWatcher : IObserver<Event>
{
    public void OnNext(Event value)
    {
        if (value is GoinToWebSiteEvent args)
            Console.WriteLine($"Użytkownik idzie do strony {args.WebAdress}");
    }

    public void OnCompleted()
    {

    }

    public void OnError(Exception error)
    { }
}

Pora na kod w konsoli demo:

var user = new User();
var watcher = new UserHistoryWatcher();

var sub = user.Subscribe(watcher);
user.GoToGoogle();
sub.Dispose();
user.GoToGoogle();
Console.ReadKey();

Jest to tylko nie wielki fragment tego co te interfejsy potrafią. Można uprościć cały proces podpinana się poprzez specjalną metodę statyczną Observable.Subscribe()

ractive extensions.PNG

Klasa Observable (nie interfejs) jest jednak częścią Reactive Extensions więc musiałbyś zdecywować czy chcesz załączyć ją do swojego projektu.

Oto jak możesz zbudować wzorzec projektowych Observer używając interfejsów w .NET bez żadnych słów kluczowych event

Główną zaletą jest możliwość zobaczenia strumienia zdarzeń, który będzie generowany przez IObservable. Całe te demo można uprościć używając metod rozszerzonych z System.Reactive.Linq;

var user = new User();
user.OfType<GoinToWebSiteEvent>().Subscribe
    (args =>Console.WriteLine($"Let's go {args.WebAdress}"));
user.GoToGoogle();

Wzorzec Observer można wykonać w C# na wiele sposobów. Warto wspomnieć, że w WPF, Win Forms, Xamarin, UWP jest także kolekcja ObservableCollection<T> stworzona z myślą o aktualizacji UI.

Co więcej, jeśli refleksja nie jest Ci straszna to możesz wykonać wzorzec projektowy observator przy pomocy atrybutów i kontenera wstrzykiwania zależności.

Pamiętaj jednak, że 99% przypadków wszystkie te mechanizm, które dziś omówiliśmy nie są wątkowo bezpieczne co znaczy, że trzeba zadbać o chaos, gdy wiele elementów naraz obserwuje siebie nawzajem.