運用泛型委派進行基本的觀察者模式實作

觀察者模式(Observer pattern)又稱為發布/訂閱模式(Publish / Subscribe pattern),可以讓A、B物件間的出現相依性,並且可以當A出現動作時,B就被觸發連動。這篇文章將利用泛型委派(Action)來進行基本的觀察者模式實作。

首先我們先建立狗類別,類別中有一個事件是OnBarking,而當我們讓狗叫Barking();的時候,他就會觸發這個OnBarking事件。

class dog
{
  public event Action OnBarking;
  public void Barking()
  {
    OnBarking();
  }
}

接著我們建立人類別,一開始的建構子我們就參考一隻狗實體,當狗叫事件出現時,就去觸發Scare();方法。

class human
{
  public human(dog d)
  {
    d.OnBarking += Scare;
  }
  public void Scare()
  {
    Console.WriteLine("Scared!");
  }
}

回到主控台,我們建立oldMan1、oldMan2實體,然後都參考到同一隻狗。當狗叫的時候,主控台上就可以看到這兩個老人都同時嚇一跳了!

static void Main(string[] args)
{
  dog oldDog = new dog();
  human oldMan1 = new human(oldDog);
  human oldMan2 = new human(oldDog);
  oldDog.Barking();
}

執行畫面如下圖:

取消事件的觀察(取消訂閱)

在真實的世界裡,一個人被狗嚇到一定是一開始的突然事件,也就是你不太可能當狗吠叫第二聲還會再嚇到一次,因此我們就用剛才的範例,接著來講事件的取消觀察、取消訂閱。

當一個人被同一隻實體的狗嚇到後,就不會在嚇到了,因此我們在嚇到的事件中,去取消訂閱。而因為我們要共用建構子中引入的狗實體,因此我們要把狗實體的指標拉升到全類別都可存取的範圍。

class human
{
  dog _d;
  public human(dog d)
  {
    _d = d;
    _d.OnBarking += Scare;
  }
  public void Scare()
  {
    Console.WriteLine("Scared!");
    _d.OnBarking -= Scare;
  }
}

然而,每次當狗吠叫時,Barking();方法每次都會去呼叫一次OnBarking();,我們可以從一開始的程式碼觀察到,Action OnBarking一開始根本就是一個空的匿名方法,更正確地來說根本是null才對。而如果我們去將唯一的訂閱方法Scare();從OnBarking中拿掉,然後又去呼叫OnBarking();,這馬上就會產生NullException了,因此,我們最好做一下檢查是否為null的防錯機制。

class dog
{
  public event Action OnBarking;
  public void Barking()
  {
    if (OnBarking != null)
      OnBarking();
  }
}

接下來就是實驗時刻了,無論這隻狗吠多少次聲音,兩個老人永遠只會嚇一跳!

static void Main(string[] args)
{
  dog oldDog = new dog();
  human oldMan1 = new human(oldDog);
  human oldMan2 = new human(oldDog);
  oldDog.Barking();  //有用;會嚇一跳
  oldDog.Barking();  //無用
  oldDog.Barking();  //無用
  oldDog.Barking();  //無用
  oldDog.Barking();  //無用
}

補充一下!如果你真的懶得寫if (OnBarking != null),你也可以選擇建立一個空白的匿名函式來當底,只是我懶得去確定到底是每一次都去檢查是否null的效率比較高,還是每一次都去跑一個空的匿名函式效率比較高。實作上建議還是使用檢查null這個方式比較好,因為你永遠不知道引用你的類別元件的使用者,要怎麼惡搞你的元件?(例如連你預設的空白匿名方法,都把它移除光了!)

class dog
{
  public event Action OnBarking = () => { };
  public void Barking()
  {
      OnBarking();
  }
}
ObserverPattern PublishAndSubscribePattern