C#設(shè)計(jì)模式之觀察者模式實(shí)例講解
前言
最近開始花點(diǎn)心思研究下設(shè)計(jì)模式,主要還是讓自己寫的代碼可重用性高、保證代碼可靠性。所謂設(shè)計(jì)模式,我找了下定義:是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。毫無疑問,設(shè)計(jì)模式于己于他人于系統(tǒng)都是多贏的;設(shè)計(jì)模式使代碼編制真正工程化;設(shè)計(jì)模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣。
為什么要提倡“Design Pattern(設(shè)計(jì)模式)”?
根本原因是為了代碼復(fù)用,增加可維護(hù)性。因此這次我們來學(xué)習(xí)下設(shè)計(jì)模式,最后會(huì)通過C#語言來實(shí)現(xiàn)這些設(shè)計(jì)模式作為例子,深刻理解其中的精髓。
定義
觀察者模式,有時(shí)被稱作發(fā)布/訂閱模式,觀察者模式定義了一種一對多的依賴關(guān)系,讓多個(gè)觀察者對象同時(shí)監(jiān)聽某一個(gè)主題對象。這個(gè)主題對象在狀態(tài)發(fā)生變化時(shí),會(huì)通知所有觀察者對象,使它們能夠自動(dòng)更新自己。
特點(diǎn)
模式中具有的角色
1。 抽象主題(Subject):它把所有觀察者對象的引用保存到一個(gè)聚集里,每個(gè)主題都可以有任何數(shù)量的觀察者。抽象主題提供一個(gè)接口,可以增加和刪除觀察者對象。
2。 具體主題(ConcreteSubject):將有關(guān)狀態(tài)存入具體觀察者對象;在具體主題內(nèi)部狀態(tài)改變時(shí),給所有登記過的觀察者發(fā)出通知。
3。抽象觀察者(Observer):為所有的具體觀察者定義一個(gè)接口,在得到主題通知時(shí)更新自己。
4。具體觀察者(ConcreteObserver):實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題狀態(tài)協(xié)調(diào)。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
一、通知通信
觀察者模式支持廣播通信。被觀察者會(huì)向所有的注冊過的觀察者發(fā)出通知。
二、聚耦合
觀察者模式在被觀察者和觀察者之間建立了一個(gè)抽象的耦合,被觀察者并不知道任何一個(gè)具體的觀察者,只是保存著抽象觀察者的列表,每個(gè)具體觀察者都符合一個(gè)抽象觀察者的接口。
缺點(diǎn):
一、時(shí)間復(fù)雜度
如果一個(gè)被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間。
二、內(nèi)聯(lián)不足
雖然觀察者模式可以隨時(shí)使觀察者知道所觀察的對象發(fā)送了變化,但是觀察者模式?jīng)]有相應(yīng)的機(jī)制使觀察者知道所觀察的對象是怎樣發(fā)生變化的。
三、容易出現(xiàn)循環(huán)調(diào)用
如果在被觀察者之間有循環(huán)依賴的話,被觀察者會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,導(dǎo)致系統(tǒng)崩潰,在使用觀察者模式應(yīng)特別注意這點(diǎn)。
實(shí)現(xiàn)思路
下面以xmfdsh發(fā)布一篇博客的例子來說明觀察者模式的實(shí)現(xiàn)。關(guān)注了xmfdsh的朋友們,便可以通過觀察者模式來實(shí)時(shí)得到博客進(jìn)行了更新的信息。當(dāng)一個(gè)抽象模型有兩個(gè)方面,其中一個(gè)方面依賴于另一個(gè)方面,將這兩者封裝在獨(dú)立的對象中以使它們可以各自獨(dú)立地改變和復(fù)用的情況下。從方面的這個(gè)詞中可以想到,觀察者模式肯定在AOP(面向方面編程)中有所體現(xiàn)。因此這種需求使用觀察者模式來解決就再恰當(dāng)不過了。
觀察者向目標(biāo)“訂閱”它的改變,而目標(biāo)發(fā)生改變后就“通知”所有已經(jīng)“訂閱”了它的改變的觀察者,從而執(zhí)行“訂閱”的內(nèi)容。這種機(jī)制的好處在于降低耦合度,分工明確,目標(biāo)只負(fù)責(zé)在自身狀態(tài)發(fā)生改變或做出某種行為時(shí)向自身的訂閱清單發(fā)出“通知”,而不是直接調(diào)用觀察者的行為(方法);觀察者只負(fù)責(zé)向目標(biāo)“訂閱”它的變化,以及定義自身在收到目標(biāo)“通知”后所需要做出的具體行為(也就是訂閱的內(nèi)容)
// 訂閱號抽象類
public abstract class Blog
{
// 保存訂閱者列表
private List<IObserver> observers = new List<IObserver>();
public string Symbol { get; set; }//描寫訂閱號的相關(guān)信息
public string Info { get; set; }//描寫此次update的信息
public Blog(string symbol, string info)
{
this.Symbol = symbol;
this.Info = info;
}
// 對同一個(gè)訂閱號,新增和刪除訂閱者的操作
public void AddObserver(IObserver ob)
{
observers.Add(ob);
}
public void RemoveObserver(IObserver ob)
{
observers.Remove(ob);
}
public void Update()
{
// 遍歷訂閱者列表進(jìn)行通知
foreach (IObserver ob in observers)
{
if (ob != null)
{
ob.Receive(this);
}
}
}
}
// 具體訂閱號類
public class MyBlog : Blog
{
public MyBlog(string symbol, string info)
: base(symbol, info)
{
}
}
// 訂閱者接口
public interface IObserver
{
void Receive(Blog tenxun);
}
// 具體的訂閱者類
public class Subscriber : IObserver
{
public string Name { get; set; }
public Subscriber(string name)
{
this.Name = name;
}
public void Receive(Blog xmfdsh)
{
Console.WriteLine("訂閱者 {0} 觀察到了{(lán)1}{2}", Name, xmfdsh.Symbol, xmfdsh.Info);
}
}
// 客戶端測試
class Program
{
static void Main(string[] args)
{
Blog xmfdsh = new MyBlog("xmfdsh", "發(fā)布了一篇新博客");
// 添加訂閱者
xmfdsh.AddObserver(new Subscriber("王尼瑪"));
xmfdsh.AddObserver(new Subscriber("唐馬儒"));
xmfdsh.AddObserver(new Subscriber("王蜜桃"));
xmfdsh.AddObserver(new Subscriber("敖尼瑪"));
//更新信息
xmfdsh.Update();
//輸出結(jié)果,此時(shí)所有的訂閱者都已經(jīng)得到博客的新消息
Console.ReadLine();
}
}
運(yùn)行的效果圖如下:
此類實(shí)現(xiàn)方法的類圖如下:
這個(gè)類圖是visual studio生成的,可能看起來比較混亂把,這樣的實(shí)現(xiàn)就是觀察者模式的實(shí)現(xiàn)。任何時(shí)候,只要執(zhí)行了Update方法,便會(huì)自動(dòng)的去通知推送給訂閱了此訂閱號 的用戶,然而在C#中,我們更多的是使用委托與事件來簡化觀察者模式的實(shí)現(xiàn)。
class Program
{
// 委托充當(dāng)訂閱者接口類
public delegate void NotifyEventHandler(object sender);
// 抽象訂閱號類
public class Blog
{
public NotifyEventHandler NotifyEvent;
public string Symbol { get; set; }//描寫訂閱號的相關(guān)信息
public string Info { get; set; }//描寫此次update的信息
public Blog(string symbol, string info)
{
this.Symbol = symbol;
this.Info = info;
}
#region 新增對訂閱號列表的維護(hù)操作
public void AddObserver(NotifyEventHandler ob)
{
NotifyEvent += ob;
}
public void RemoveObserver(NotifyEventHandler ob)
{
NotifyEvent -= ob;
}
#endregion
public void Update()
{
if (NotifyEvent != null)
{
NotifyEvent(this);
}
}
}
// 具體訂閱號類
public class MyBlog : Blog
{
public MyBlog(string symbol, string info)
: base(symbol, info)
{
}
}
// 具體訂閱者類
public class Subscriber
{
public string Name { get; set; }
public Subscriber(string name)
{
this.Name = name;
}
public void Receive(Object obj)
{
Blog xmfdsh = obj as Blog;
if (xmfdsh != null)
{
Console.WriteLine("訂閱者 {0} 觀察到了{(lán)1}{2}", Name, xmfdsh.Symbol, xmfdsh.Info);
}
}
}
static void Main1(string[] args)
{
Blog xmfdsh = new MyBlog("xmfdsh", "發(fā)布了一篇新博客");
Subscriber wnm = new Subscriber("王尼瑪");
Subscriber tmr = new Subscriber("唐馬儒");
Subscriber wmt = new Subscriber("王蜜桃");
Subscriber anm = new Subscriber("敖尼瑪");
// 添加訂閱者
xmfdsh.AddObserver(new NotifyEventHandler(wnm.Receive));
xmfdsh.AddObserver(new NotifyEventHandler(tmr.Receive));
xmfdsh.AddObserver(new NotifyEventHandler(wmt.Receive));
xmfdsh.AddObserver(new NotifyEventHandler(anm.Receive));
xmfdsh.Update();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("移除訂閱者王尼瑪");
xmfdsh.RemoveObserver(new NotifyEventHandler(wnm.Receive));
xmfdsh.Update();
Console.ReadLine();
}
}
運(yùn)行的結(jié)果:
類圖:
總結(jié)
到這里,觀察者模式就講完了,觀察者模式定義了一種一對多的依賴關(guān)系,讓多個(gè)觀察者對象可以同時(shí)監(jiān)聽某一個(gè)主題對象,這個(gè)主題對象在發(fā)生狀態(tài)變化時(shí),會(huì)通知所有觀察者對象,使它們能夠自動(dòng)更新自己,因此在一些需求上是當(dāng)一個(gè)對象的改變需要同時(shí)改變多個(gè)其他對象的時(shí)候,且不知道多少個(gè)對象需要去通知改變的時(shí)候,觀察者模式就成了首選,這種模式的用的最多的,在我的開發(fā)經(jīng)歷中便是windows phone手機(jī)客戶端的開發(fā)了,經(jīng)常要用到這類的委托事件的處理,用多了后發(fā)現(xiàn)就習(xí)以為常,這種模式也就沒那么稀奇了。
源碼下載地址:http://xiazai.jb51.net/201410/tools/ConsoleApplication2.rar
相關(guān)文章
C# 使用 Castle 實(shí)現(xiàn) AOP及如何用 Autofac 集成 Castle
這篇文章主要介紹了C# 使用 Castle 實(shí)現(xiàn) AOP及如何用 Autofac 集成 Castle,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-02-02C#并發(fā)實(shí)戰(zhàn)記錄之Parallel.ForEach使用
這篇文章主要給大家介紹了關(guān)于C#并發(fā)實(shí)戰(zhàn)記錄之Parallel.ForEach使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Unity Shader實(shí)現(xiàn)水波紋效果
這篇文章主要為大家詳細(xì)介紹了Unity Shader實(shí)現(xiàn)水波紋效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05