设计模式之观察者模式
时间: 2017-11-30 15:39:09 来源:博客园
一、引言
今天是2017年11月份的最后一天,也就是2017年11月30日,利用今天再写一个模式,争取下个月(也就是12月份)把所有的模式写完,2018年,新的一年写一些新的东西。今天我们开始讲“行为型”设计模式的第四个模式,该模式是【观察者模式】,英文名称是:Observer
Pattern。还是老套路,先从名字上来看看。“观察者模式”我第一次看到这个名称,我的理解是,既然有“观察者”,那肯定就有“被观察者”了,“观察者”监视着“被观察者”,如果“被观察者”有所行动,“观察者”就会做出相应的动作来回应,哈哈,听起来是不是有点像“谍战”的味道。我所说的谍战不是天朝内的那种,比如:手撕鬼子,我说的是“谍影重重”的那类优秀影片,大家懂得。“观察者模式”在现实生活中,实例其实是很多的,比如:八九十年代我们订阅的报纸,我们会定期收到报纸,因为我们订阅了。银行可以给储户发手机短信,也是“观察者模式”很好的使用的例子,因为我们订阅了银行的短信业务,当我们账户余额发生变化就会收到通知,还有很多,我就不一一列举了,发挥大家的想象吧。好了,接下来,就让我们看看该模式具体是怎么实现的吧。
二、观察者模式的详细介绍
2.1、动机(Motivate)
在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
2.2、意图(Intent)
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。 ——《设计模式》GoF
2.3、结构图
2.4、模式的组成
可以看出,在观察者模式的结构图有以下角色:
(1)、抽象主题角色(Subject):抽象主题把所有观察者对象的引用保存在一个列表中,并提供增加和删除观察者对象的操作,抽象主题角色又叫做抽象被观察者角色,一般由抽象类或接口实现。
(2)、抽象观察者角色(Observer):为所有具体观察者定义一个接口,在得到主题通知时更新自己,一般由抽象类或接口实现。
(3)、具体主题角色(ConcreteSubject):实现抽象主题接口,具体主题角色又叫做具体被观察者角色。
(4)、具体观察者角色(ConcreteObserver):实现抽象观察者角色所要求的接口,以便使自身状态与主题的状态相协调。
2.5、观察者模式的代码实现
观察者模式在显示生活中也有类似的例子,比如:我们订阅银行短信业务,当我们账户发生改变,我们就会收到相应的短信。类似的还有微信订阅号,今天我们就以银行给我发送短信当我们账户余额发生变化的时候为例来讲讲观察者模式的实现,很简单,现实生活正例子也很多,理解起来也很容易。我们看代码吧,实现代码如下:
1 namespace 观察者模式的实现 2 { 3 //银行短信系统抽象接口,是被观察者--该类型相当于抽象主体角色Subject 4 public abstract class BankMessageSystem 5 { 6 private IList<Depositor> observers; 7 8 //构造函数初始化观察者列表实例 9 protected BankMessageSystem() 10 { 11 observers=new List<Depositor>(); 12 } 13 14 //增加预约储户 15 public abstract void Add(Depositor depositor); 16 17 //删除预约储户 18 public abstract void Delete(Depositor depositor); 19 20 //通知储户 21 public void Notify() 22 { 23 foreach(Depositor depositor in observers) 24 { 25 if(depositor.AccountIsChanged) 26 { 27 depositor.Update(moneyBalance.MoneyBalance,moneyBalance.OperationDateTime); 28 //账户发生了变化,并且通知了,储户的账户就认为没有变化 29 depositor.AccountIsChanged=false; 30 } 31 } 32 } 33 } 34 35 //北京银行短信系统,是被观察者--该类型相当于具体主体角色ConcreteSubject 36 public sealed class BeiJingBankMessageSystem:BankMessageSystem 37 { 38 //增加预约储户 39 public override void Add(Depositor depositor) 40 { 41 //应该先判断该用户是否存在,存在不操作,不存在则增加到储户列表中,这里简化了 42 observers.Add(depositor); 43 } 44 45 //删除预约储户 46 public override void Delete(Depositor depositor) 47 { 48 //应该先判断该用户是否存在,存在则删除,不存在无操作,这里简化了 49 observers.Remove(depositor); 50 } 51 } 52 53 //储户的抽象接口--相当于抽象观察者角色(Observer) 54 public abstract class Depositor 55 { 56 //状态数据 57 private string _name; 58 private int _balance; 59 private int _total; 60 private bool _isChanged; 61 62 //初始化状态数据 63 protected Depositor(string name,int total) 64 { 65 this._name=name; 66 this._balance=total;//存款总额等于余额 67 this._isChanged=false;//账户未发生变化 68 } 69 70 //储户的名称,假设可以唯一区别的 71 public string Name 72 { 73 get{return _name;} 74 private set{ this._name=value;} 75 } 76 77 public int Balance 78 { 79 get{return this._balance;} 80 } 81 82 //取钱 83 public void GetMoney(int num) 84 { 85 if(num<=this._balance && num>0) 86 { 87 this._balance=this._balance-num; 88 this._isChanged=true; 89 OperationDateTime=DateTime.Now; 90 } 91 } 92 93 //账户操作时间 94 public DateTime OperationDateTime{get;set;} 95 96 //账户是否发生变化 97 public bool AccountIsChanged 98 { 99 get{return this._isChanged;}100 set{this._isChanged=value;}101 }102 103 //更新储户状态104 public abstract void Update(int currentBalance,DateTime dateTime);105 }106 107 //北京的具体储户--相当于具体观察者角色ConcreteObserver108 public sealed class BeiJingDepositor:Depositor109 {110 pubic void Update(int currentBalance,DateTime dateTime)111 {112 Console.WriteLine(Name+":账户发生了变化,变化时间是"+dateTime.ToString()+",当前余额是"+currentBalance.ToString());113 }114 }115 116 117 // 客户端(Client)118 class Program119 {120 static void Main(string[] args)121 {122 //我们有了三位储户,都是武林高手,也比较有钱123 Depositor huangFeiHong=new BeiJingDepositor("黄飞鸿",3000);124 Depositor fangShiYu=new BeiJingDepositor("方世玉",1300);125 Depositor hongXiGuan=new BeiJingDepositor("洪熙官",2500);126 127 BankMessageSystem beijingBank=new BeiJingBankMessageSystem();128 //这三位开始订阅银行短信业务129 beijingBank.Add(huangFeiHong);130 beijingBank.Add(fangShiYu);131 beijingBank.Add(hongXiGuan);132 133 //黄飞鸿取100块钱134 huangFeiHong.GetMoney(100);135 beijingBank.Notify();136 137 //黄飞鸿和方世玉都取了钱138 huangFeiHong.GetMoney(200);139 fangShiYu.GetMoney(200);140 beijingBank.Notify();141 142 //他们三个都取了钱143 huangFeiHong.GetMoney(320);144 fangShiYu.GetMoney(4330);145 hongXiGuan.GetMoney(332);146 beijingBank.Notify();147 148 Console.Read();149 }150 }151 }
观察者模式有些麻烦的地方就是关于状态的处理,我这里面涉及了一些状态的处理,大家可以细细体会一下,模式还是要多多练习,多多写,里面的道理就不难理解了。
三、观察者模式的实现要点:
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者(面向对象中的改变不是指改代码,而是指扩展、子类化、实现接口),从而使二者之间的依赖关系达致松耦合。
目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知,目标对象对此一无所知。
在C#的event中,委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象。委托是比抽象Observer接口更为松耦合的设计。
观察者模式有以下几个优点:
(1)、观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
(2)、观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
(3)、观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。
观察者也存在以下一些缺点:
(1)、如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
(2)、虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
(3)、如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。
四、.NET 中观察者模式的实现
我上面写了一点,“在C#的event中,委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象。委托是比抽象Observer接口更为松耦合的设计。”,其实在Net里面实现的观察者模式做了一些改变,用委托或者说是事件来实现观察者模式。事件我们都很明白,我们可以注册控件的事件,当触发控件的动作时候,相应的事件就会执行,在事件的执行过程中我们就可以做相关的提醒业务。这里关于观察者模式在Net里面的实现就不说了,如果大家不明白,可以多看看相关委托或者事件的相关资料。
五、总结
终于写完了,这个模式主要是花在了代码的书写上。因为我写每篇文章的时候,模式实现代码都是当时现想的,要组织代码关系,让其更合理,所以时间就花了不少,但是是理解更好了。该模式不是很难,结构也不是很复杂,唯一让我们多多注意的是状态的管理。这个模式结合实例理解是很容易的,模式的使用我们不能照搬,要理解,当然多多的联系和写代码也是必不可少的,我们使用模式的一贯宗旨是通过重构和迭代,在我们的代码中实现相应的模式。