C# - Dabble with IObservable<T> and IObserver<T>

Delegate offers some nice features of observable pattern, however, there are some limiation on the delgate implemented observable patterns.

 

Why not Delegate

 

For onething, there is no way to know when the subject no longer tick; the subject may become out of interest if it does not tick updates; But with the passive mode of delegate, you do not know that a subject is become inactive as the pattern itself is passive (there are some workaround , indeed by you provide another event to nofity that the subject is beome inactive);

 

For another, there is no way to descrimate against a valid udpate from error conditions, using some sentry value as the update event args could serve in some cases, but that is a botch/tinker/bumble/bungle rather than good solution, it is a smell design to intermingle the normal message channel with the error message channel;

 

for yet another, there is no way to know when/how to remove the IObserver from the notification list, a simple -= may unsusbscribe the event listener, but there would exist a huge amount of house keeping work that needs to be addresseed when an Observer go out of scope. Better, a Disposable is better in situation like this; 

 

 

IObservable<T> and IObserver<T> pattern provides the cure;

 

It has provide the following.

the return value of IDisposable, which satisify the the question of "how to remove teh IObserver from the notification list";

this has the functionality of telling the observers when the subject (provider) will/has become inactive. and on receiving the information, teh IObserver can use the IDispose method that it receives in the Subscribe step to unsubscibe itself from the notification and do the proper uninitialization.

  • IObserver<T>.OnNext(): this satisfy the basic requirement of the Provider notifies the clien when there is update coming.

 

Below shows the code an example IObservable<T> implementation, the T is of type Location, which represent some Location information.

 

 

 

namespace PushBasedNotificationTest
{
  public struct Location
  {

    double lat, lon;

    public Location(double latitude, double longitude)
    {
      this.lat = latitude;
      this.lon = longitude;
    }

    public double Latitude
    {
      get { return this.lat; }
    }
    public double Longitude
    {
      get { return this.lon; }
    }

  }
}
 

 

LocationTracker is our impl  of IObservable<Location>, here it is:

 

 

namespace PushBasedNotificationTest
{
  public class LocationTracker  : IObservable<Location>
  {
    public LocationTracker()
    {
      // initialize the list of Observers to the Updates
      observers = new List<IObserver<Location>>();
    }

    // you need a collection of Observers
    // on which you can send the notification 
    private List<IObserver<Location>> observers;


    // Subscribe an observer (IObserver<T>) to the observer list
    // notice the return value of the Subscribe method, 
    // which is of type IDisposable.
    // normally the Observer will store the return value 
    // and call the Dispose method from the IDisposable interface 
    // the common work done in the Disposable method is to remove itself from the 
    // notification list;
    public IDisposable Subscribe(IObserver<Location> observer)
    {
      if (!observers.Contains(observer))
        observers.Add(observer);

      return new Unsubscriber(observers, observer);

    }


    // the Unsubscriber will be returned to Observer
    // which on dispose, can remove itself from the 
    // notification list, 
    // so it makes sense to extends the Unsubscriber from the 
    // interface IDisposable
    // 
    // Another note about the IDisposable implementation is that 
    // the IObservable interface can hide the implementation inside its body
    // client to IObservable should not be aware of the implementation of the 
    // interface, thus have better decoupling
    private class Unsubscriber : IDisposable
    {

      public Unsubscriber(List<IObserver<Location>> observers, IObserver<Location> observer)
      {
        _observers = observers;
        _observer = observer;
      }


      private List<IObserver<Location>> _observers;
      private IObserver<Location> _observer;

      public void Dispose()
      {
        if (_observer != null && _observers.Contains(_observer))
          _observers.Remove(_observer);
      }

    }

    // though I think it is better to call the method Update
    // as what it does it to notify the observers that has registered to
    // itself ; 
    // the notification is done by send message to the 
    //  OnComplete
    //  OnNext
    //  OnError
    // method of the IObserver<T> interface
    // the IObserver<T> interface can decide what action to take on 
    // receiving the message on different channels.
    // 
    public void TrackLocation(Nullable<Location> loc)
    {
      foreach (var observer in observers)
      {
        if (!loc.HasValue)
        {
          observer.OnError(new LocationUnknownException());
        }
        else
        {
          observer.OnNext(loc.Value);
        }
      }
    }

    // Or I think a better name could be 
    // EndUpdate
    // because what it does it to signal the end of updates 
    // from this very IObservable interface
    public void EndTransmission()
    {
      foreach (var observer in observers.ToArray())
      {
        if (observers.Contains(observer))
          observer.OnCompleted();
      }
      // Is it necessary to do the 
      //  IList.Clear() call?
      // because the IObserver<T> method should have the necessary Dispose method to remove 
      // the list...
      // However, I think as a defensive means, it is necessary
      observers.Clear();
    }
  }
}
 

and the LocationReporter is the Observer which implements the IObserver<Location> interface:

 

 

namespace PushBasedNotificationTest
{
  public class LocationReporter : IObserver<Location>
  {
    // Remeber what we have said before, we subscribe the IObserver
    // and we get back a IDisposable 
    // instance, the intance will helps us do the necessary disposition
    private IDisposable unsubscriber;

    private string instName;

    public LocationReporter(string name)
    {
      this.instName = name;
    }

    public string Name { get { return instName; } }


    // you can further encapsulate the IObservable<T> interface.Subscriber method
    // by the encapsulation, you can store the return value of the 
    // IDisposable instance locally.
    // The benefit is that you can chain the Disposble method to destroy the 
    // subscription once the ISubscriber is dead
    public virtual void Subscribe(IObservable<Location> provider)
    {
      if (provider != null)
        unsubscriber = provider.Subscribe(this);
    }

    public virtual void OnCompleted()
    {
      Console.WriteLine("The Location Tracker has completed transmitting data to {0}.", this.Name);
    }

    // The contract of the OnError is Exception E
    // where you can have subsclass of Exception passed in to indicate a specific kind of 
    // error has happened
    public virtual void OnError(Exception e)
    {
      Console.WriteLine("{0}: The locatino cannot be determined.", this.Name);
    }

    public virtual void OnNext(Location value)
    {
      Console.WriteLine("{2}: the current location is {0}, {1}", value.Latitude, value.Longitude, this.Name);
    }

    // you can actually inherits the LocationReporter from IDisposable
    // where you can chain the Dispose method together
    public virtual void Unsubscribe()
    {
      unsubscriber.Dispose();
    }
  }
}

 

 

To communicate the eror condition, we will use the LocationException message as follow. 

 

 

 

namespace PushBasedNotificationTest
{
  //  this is a custom type Exception  that 
  // you may pass to the OnError Notification channel of
  // IObserver<T> interface.
  public class LocationUnknownException : Exception
  {
    internal LocationUnknownException()
    { }
  }
}
 

The last point, is the drive class that wires up the Observer and their Observables.

 

 

namespace PushBasedNotificationTest
{
  class Program
  {
    static void Main(string[] args)
    {
      // Define a provider and two observers
      LocationTracker provider = new LocationTracker();
      LocationReporter reporter1 = new LocationReporter("FixedGPS");
      reporter1.Subscribe(provider);
      LocationReporter reporter2 = new LocationReporter("MobileGPS");
      reporter2.Subscribe(provider);

      provider.TrackLocation(new Location(47.6456, -122.1312));
      reporter1.Unsubscribe();
      provider.TrackLocation(new Location(47.6677, -122.1199));
      provider.TrackLocation(null); // trigger the error condition
      provider.EndTransmission();
    }
  }
}
 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值