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

本文探讨了Observable模式在实现通知机制时的局限性,并通过引入IObservable<T>和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();
    }
  }
}
 

 

 

 

module BCD ( input clk, input rst_n, input [7:0] data_in, input flag, output reg [3:0] baiwei, output reg [3:0] shiwei, output reg [3:0] gewei ); // 内部信号定义 reg [7:0] data_reg; reg [39:0] data_shift; // 8-bit binary -> up to 255, needs 3 BCD digits (12 bits), but we use 40-bit shift register for safety reg [3:0] cnt_shift; wire shift_flag; // 状态控制:当 flag 上升沿触发时开始转换 reg flag_d, start_convert; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin flag_d <= 1'b0; start_convert <= 1'b0; end else begin flag_d <= flag; start_convert <= flag & ~flag_d; // detect rising edge of flag end end // 控制计数器:用于控制移位次数(共8次移位 + 初始化) always @(posedge clk or negedge rst_n) begin if (!rst_n) cnt_shift <= 4'd0; else if (start_convert) cnt_shift <= 4'd0; // reset counter else if (cnt_shift < 8) cnt_shift <= cnt_shift + 1; // stay at 8 after done end // shift_flag 表示正在移位过程中(除了初始化) assign shift_flag = (cnt_shift > 0) && (cnt_shift <= 8); // 主 Double-Dabble 移位寄存器逻辑 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin data_shift <= 40'd0; end else if (start_convert) begin // Load initial value: zero-extended data_in into lower 8 bits data_shift <= {32'd0, data_in}; // [39:8] = 0, [7:0] = data_in end else if (shift_flag) begin // Step 1: Add 3 correction before shifting, for each BCD digit that is >= 5 // We have 3 BCD digits: [11:0] -> [11:8] (hundreds), [7:4] (tens), [3:0] (units) // Temporary variable to hold corrected value reg [39:0] temp; temp = data_shift; // Apply +3 correction to any 4-bit group that has value >= 5 if (temp[3:0] > 4) temp[3:0] = temp[3:0] + 3; // units if (temp[7:4] > 4) temp[7:4] = temp[7:4] + 3; // tens if (temp[11:8] > 4) temp[11:8] = temp[11:8] + 3; // hundreds // higher digits not used in 8-bit case // Step 2: Left shift the entire register data_shift <= temp << 1; end // Hold state otherwise end // 输出赋值:在转换完成后提取结果 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin baiwei <= 4'd0; shiwei <= 4'd0; gewei <= 4'd0; end else if (cnt_shift == 8) begin // conversion complete gewei <= data_shift[3:0]; // lower 4 bits = units shiwei <= data_shift[7:4]; // middle 4 bits = tens baiwei <= data_shift[11:8]; // upper 4 bits = hundreds end end endmodule 为什么百位会出现8,9?
最新发布
10-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值