程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .net設計模式之觀察者模式

.net設計模式之觀察者模式

編輯:關於.NET

故事

小雪是一個非常漂亮的女孩,漂亮的女孩總是有很多的追求者,而且追求者的隊伍在不斷的變動,隨時有人進入這個隊伍,也有人退出。男孩們追求女孩時總是表現出120%的關心,當小雪私自游玩時總是不斷收到追求者詢問小雪位置變動的消息,小雪也不勝其煩,但小雪是如此的一個善良的女孩,她總是打斷自己正常的生活回復男孩們的消息。而男孩們由於要不斷的關心小雪的位置變化也弄的精疲力竭,而且還影響正常的工作。

在這樣一個簡單的故事場景中我們發現了什麼?來看看小雪和男孩們的煩惱:

男孩們必須不斷的詢問小雪的位置變化,從而打斷正常的工作

小雪也要不斷的接受男孩們的詢問,有的時候小雪的位置並沒有發生變化,還是要不斷的回復男孩們的詢問,也影響正常的工作。

如果給各個男孩們回復問題的方式都不盡相同,小雪還要知道不同的回復方式,而且不斷的有新的男孩們增加進來,還不知道未來有什麼新的回復方式。

看到這麼多煩惱,我們創意無限的Nokia公司給小雪和男孩們提出了解決方案:

Nokia公司榮譽出品了一款帶有GPRS功能的手機,該手機保存著一個訂閱位置變化短信通知的電話列表,當該手機檢測到位置發生變化就會向這個訂閱列表裡的所有手機發送短信。看到Nokia這個解決方案,男孩們和小雪都應該松一口氣,他們各自都可以按照自己正常的生活習慣,只有狀態發生變化時候各自才會進行通信。

觀察者模式的解決方案

在上面Nokia的解決方案中就透露出觀察者模式的思想:觀察者模式定義了對象之間一對多的依賴,當這個對象的狀態發生改變的時候,多個對象會接受到通知,有機會做出反饋。在運行的時刻可以動態的添加和刪除觀察者。

帶著這個定義我們來看看嘗試實現上面的觀察者模式

首先在觀察者模式中我們必須定義一個所有“觀察者”都必須實現的接口,這樣被觀察者向觀察者發送消息的時候就可以使用統一的方式,這也符合面相對象原則中的面向接口編程:

//所有觀察者都必須實現
public interface IBoy
{
//向男孩們顯示小雪位置情況,也就是向觀察者發送消息,觀察者還可以對此做出反饋
void Show(string address);
}
using System;
//男孩A,一個觀察者
public class BoyA : IBoy
{
public void Show(string address)
{
//假設經過處理後為韓文的地址
Console.WriteLine("A:"+address);
}
}
using System;
//男孩B,又一個觀察者
public class BoyB : IBoy
{
public void Show(string address)
{
//假設經過處理後為英語的地址
Console.WriteLine("B:"+address);
}
}

下面看看小雪的實現,也就是被觀察者,主要看看那個訂閱的電話列表和怎樣將消息通知給觀察者.

using System;
using System.Collections.Generic;
public class GPRSMobile
{
//保存一個觀察者列表
private List<IBoy> boys = null;
private string address = "";
public GPRSMobile()
{
boys = new List<IBoy>();
}
//添加觀察者
public void AddBoy(IBoy b)
{
boys.Add(b);
}
public void RemoveBoy(IBoy b)
{
boys.Remove(b);
}
//通知
private void Notify(string address)
{
for (int i = 0; i < boys.Count; i++)
{
boys[i].Show(address);
}
}
//位置發生變化
public void OnAddressChanaged(string newAddress)
{
//假設這裡的地址是中文形式的
Notify(newAddress);
}
}

看到上面的代碼例子,我們可以給觀察者模式的實現總結出這樣幾條規律:第一,被觀察者必須保存著一個觀察者列表。第二,所有的觀察者必須實現一個統一的接口。

那觀察者模式到底有哪些好處呢?在上面的例子中我們可以看到被觀察者僅僅依賴於一個實現了觀察者接口的列表,我們可以隨時的向這個列表添加觀察者,而被觀察者無須關注觀察者是如何實現的。當我們向觀察者族中添加一個新的觀察者,被觀察者無須作任何改變,新的類型只要實現了觀察者接口即可。

在上面的描述中我們仿佛看到了面向接口編程無窮的力量,面向接口編程使實現依賴於接口,也就是實現依賴於抽象。這樣在被依賴對象發生改變的時候,只要接口沒有發生變化時,依賴對象無須作任何變動。

在現實中存在著很多觀察者模式的實例。比如在這個全民炒股的時代,每個持有股票的人總是不斷的關注著自己所買的股票的走勢,有人天天呆在交易大廳裡看著屏幕上股票價格的走勢,有人在工作時間盯著電腦裡股票軟件,為此很多公司采取各種各樣的政策來制止這種行為,這樣不僅影響正常的上班,且股票交易大廳常常人滿為患。如果有這樣一個服務,只要你訂閱一個短信,這個服務就會在你所關注的股票價格發生變動的時候短信通知你,這樣你就可以按照正常的順序來做你的工作。

.net中的觀察者模式

在.net中,微軟給我們帶來一個更好的觀察者模式的實現:事件-委托.

在Gof的觀察者模式中(姑且稱之為經典設計模式吧),觀察者必須實現一個統一的接口,在.net裡這個接口由委托的簽名來保證了,.net裡的委托就是一個安全的函數指針(之所以說安全是與以前的C指針相比的,C的函數指針並不包括函數的簽名比如參數等東西,所以可以傳遞一個並不是你期望的函數進去,導致運行時出錯,由於這種錯誤在運行時發生,很難檢查出來)。Ok,現在以一個.net的委托-事件的例子結束今天的觀察者模式吧。

描述:這是一個控制台程序,程序接收一個0到100之間整型的輸入,程序接收到輸入後開始一個從0到100的循環,當循環到你輸入的數字的時候做一些處理,我們將以兩種方式來描述這個實例,先用常規的方式,然後采用委托-事件的方式

public class Program
{
static void Main(string[] args)
{
Console.WriteLine("Please Input a 0-100 Number:");
int input = Console.Read();
if (input < 0 || input > 100)
{
Console.WriteLine("Error");
}
for (int i = 0; i <= 100; i++)
{
if (i == input)
{
//屏幕輸出
Console.WriteLine(i.ToString());
//彈出提示框
MessageBox.Show(i.ToString());
//可能還有其他處理
}
}
}
}

看到這個例子有什麼感覺?循環的代碼和處理的代碼混在一起,可能還有未知的處理方式添加進來。耦合度非常高。再看看.net的處理方式吧

namespace Observer
{
//定義一個委托,這裡定義了觀察者方法的簽名,就是一個協議吧
public delegate void NumberEventHandler(object sender,NumberEventArgs e);
//要傳遞哪些參數到觀察者?在這裡定義,注意,要繼承自EventArgs
public class NumberEventArgs : EventArgs
{
public NumberEventArgs(int number)
{
_number = number;
}
private int _number;
public int Number
{
get { return _number;}
set { _number = value;}
}
}
//觀察者模式中的主題
public class Subject
{
//定義一個事件,就是委托的實例了
public event NumberEventHandler NumberReached;
public void DoWithLoop(int number)
{
for (int i = 0; i <= 100; i++)
{
//觸發事件的條件到了
if (i == number)
{
NumberEventArgs e = new NumberEventArgs(i);
OnNumberReached(e);
}
}
}
//注意,這個方法定義為保護的,虛擬的,代表子類還可以進行覆蓋,改變觸發事件的行為
//甚至可以不觸發事件
protected virtual void OnNumberReached(NumberEventArgs e)
{
//判斷事件是否為null,也就是是否綁定了方法
if (NumberReached != null)
NumberReached(this, e);
}
}
  
public class MainProgram
{
public static void Main()
{
Console.WriteLine("Please Input a 0-100 Number:");
int input = Console.Read();
if (input < 0 || input > 100)
{
Console.WriteLine("Error");
}
Subject s = new Subject();
//給事件綁定方法,靜態的
s.NumberReached += new NumberEventHandler(msgbox_NumberReached);
MainProgram mp = new MainProgram();
//給事件綁定方法,實例方法
s.NumberReached += new NumberEventHandler(mp.console_NumberReached);
s.DoWithLoop(input);
Console.Read();
}
void console_NumberReached(object sender, NumberEventArgs e)
{
Console.WriteLine(e.Number.ToString());
}
static void msgbox_NumberReached(object sender, NumberEventArgs e)
{
MessageBox.Show(e.Number.ToString());
}
}
}

雖然這個例子代碼多多了,但是是值得的,事件觸發的地方和處理的地方完全分離了,循環的位置不再需要知道有多少個方法正等著處理它

總結

經過幾篇設計模式文章的介紹,也許有人會覺得設計模式一直在嘗試解決幾個問題:解藕,封裝變化。設計模式一直在為可維護性,可擴展性,靈活性努力著。所以學習設計模式並不是了解設計模式的原型,重要的是了解設計模式的場景和目的,這樣你也可以在你自己的工作中總結出自己的設計模式。

有人說中國的數學教育是個錯誤,學習數學並不是學習那些定理公式,學習那些東西永遠是跟在別人的後面,學習數學應該注重數學史的學習,循著數學發展的歷史,了解前人是怎樣分析問題,解決問題,學習前人的“漁”,並不僅僅是為了得到“魚”。

本來上面的文章已經寫定了,但今天看一MVP的文章又有點新的感觸,覺得上面的總結又有點偏頗,學習模式重要的是她的精髓,但是“作為初學者即使知道所有設計原則但是卻不知道如何在項目應用”。是的,也許學習設計模式也要從“量變”引起“質變”。大量的應用,先不管是否是過度設計,到一定的時候也許就會得到思想上的升華。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved