作业三_C#中的观察者模式解析
一、观察者模式的理解分析
由于之前没有接触过观察者模式,所以找了一段源码编译运行调试一下。下面是观察者模式用C#模拟实现的示意源码。该段代码模拟了观察者模式的一个运行原理,其可以观察到被观察对象的动态。(你已经设置好的想看到的动态。)
下面是代码源码和注释以及运行过程:
示例代码:
1 using System; 2 using System.Collections; 3 namespace CSharp_Observe 4 { 5 class Program 6 { 7 // Fields 8 private ArrayList observers = new ArrayList(); //在program类中定义一个动态私有数组 取名observers 9 // Methods 10 public void Attach(Observer observer) 11 { 12 observers.Add(observer); //在该类中定义添加功能 给私有成员observes添加内容 13 } 14 public void Detach(Observer observer) 15 { 16 observers.Remove(observer); //在该类中定义移除功能 给私有成员observes移除内容 17 } 18 public void Notify() 19 { 20 foreach (Observer o in observers) //遍历该数组 每次都执行更新 21 o.Update(); 22 } 23 } 24 class ConcreteSubject : Program // 继承了Program类 25 { 26 // Fields 27 private string subjectState; 28 // Properties 29 public string SubjectState 30 { 31 get { return subjectState; } 32 set { subjectState = value; } 33 } 34 } 35 // "Observer" 36 abstract class Observer 37 { 38 // Methods 39 abstract public void Update(); //定义抽象方法 观察者 40 } 41 // "ConcreteObserver" 42 class ConcreteObserver : Observer 43 { 44 // Fields 45 private string name; 46 private string observerState; 47 private ConcreteSubject subject; 48 // Constructors 49 public ConcreteObserver(ConcreteSubject subject, 50 string name) 51 { 52 this.subject = subject; 53 this.name = name; 54 } 55 // Methods 56 override public void Update() 57 { 58 observerState = subject.SubjectState; 59 Console.WriteLine("Observer {0}'s new state is {1}", 60 name, observerState); 61 } 62 // Properties 63 public ConcreteSubject Subject 64 { 65 get { return subject; } 66 set { subject = value; } 67 } 68 } 69 public class Client 70 { 71 public static void Main(string[] args) 72 { 73 // 先声明出基础的数据 以及初始化操作 74 ConcreteSubject s = new ConcreteSubject(); 75 s.Attach(new ConcreteObserver(s, "1")); 76 s.Attach(new ConcreteObserver(s, "2")); 77 s.Attach(new ConcreteObserver(s, "3")); 78 // 改变s 并且通知观察者 也就是调用函数 Notify 79 s.SubjectState = "ABC"; 80 s.Notify(); 81 Console.ReadKey(); 82 } 83 } 84 }
从这段程序以及查阅资料了解到,观察者模式的含义是:定义对象间一种一对多的依赖关系,是的没当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。
比如其中的发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息改变状态。(这里其实可以做到当一个状态改变时,另一个状态也跟着改变,为编程提供了很大的方便)
一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其它的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作(Collaboration)。观察者模式是满足这一要求的各种设计方案中最重要的一种。
通过查阅资料了解到观察者模式一般有以下几种角色:
抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。
二,实例解析观察者模式:
每种编程架构及程序语言,对观察者模式都有不通的具体实现。在.NET框架中,C#语言使用委托以及事件,可以很好的实现观察者模式。委托相当于“订阅清单”的角色,当目标中关联了该委托的事件被触发时,则委托将自动按序执行观察者注册于委托中的方法。
模型需要做的只是声明委托以及声明委托类型的事件。当然,还可以附加上封装了触发委托事件的方法。所有派生自模型的类都是具体目标,它们所要做的只是在适当的场合触发事件。(即发出“通知”)。
通过对模型与观察者基类的分析可知,委托与事件的机制几乎消除了这两个模块之间的耦合,灵活性提高了很多。如果需要增加观察者,则只需要覆盖基类抽象方法及把观察目标传递给基类。
具体示例:
1、首先是建立模型,把目标基类建立好。
2、建立观察者基类,在这里分为单行为和多行为的。
2.1 首先是单行为基类的建立。
1 /**//// <summary> 2 /// 在Observer Pattern(观察者模式)中,此类作为所有Observer(观察者)的抽象基类 3 /// 所有要充当观察者的类(在此事例中为"老鼠"和"人")都继承于此类. 4 /// 我们说此类作为观察者基类,用于规划所有观察者(即订阅方)订阅行为. 5 /// 在此事例中,规划了针对目标基类(ModelBase)中声明的"无参无返回"委托的一个 6 /// 方法(Response),并于构造该观察者时将其注册于具体目标(参数传递)的委托事件中. 7 /// 具体实施过程: 8 /// 1.指定观察者所观察的对象(即发布方).(通过构造器传递) 9 /// 2.规划观察者自身需要作出响应方法列表 10 /// 3.注册需要委托执行的方法.(通过构造器实现) 11 /// </summary> 12 public abstract class Observer 13 { 14 /**//// <summary> 15 /// 构造时通过传入模型对象,把观察者与模型关联,并完成订阅. 16 /// 在此确定需要观察的模型对象. 17 /// </summary> 18 /// <param name="childModel">需要观察的对象</param> 19 public Observer(ModelBase childModel) 20 { 21 //订阅 22 //把观察者行为(这里是Response)注册于委托事件 23 childModel.SubEvent+=new ModelBase.SubEventHandler(Response); 24 } 25 26 /**//// <summary> 27 /// 规划了观察者的一种行为(方法),所有派生于该观察者基类的具体观察者都 28 /// 通过覆盖该方法来实现作出响应的行为. 29 /// </summary> 30 public abstract void Response(); 31 }
2.2、多行为基类的建立。
1 /**//// <summary> 2 /// 定义了另一个观察者基类.该观察者类型拥有两个响应行为. 3 /// 并在构造时将响应行为注册于委托事件. 4 /// (具体描述请参照另一观察者基类Observer) 5 /// </summary> 6 public abstract class Observer2 7 { 8 /**//// <summary> 9 /// 构造时通过传入模型对象,把观察者与模型关联,并完成订阅. 10 /// 在此确定需要观察的模型对象. 11 /// </summary> 12 /// <param name="childModel">需要观察的对象</param> 13 public Observer2(ModelBase childModel) 14 { 15 //订阅 16 //把观察者行为(这里是Response和Response2)注册于委托事件 17 childModel.SubEvent+=new ModelBase.SubEventHandler(Response); 18 childModel.SubEvent+=new ModelBase.SubEventHandler(Response2); 19 20 } 21 /**//// <summary> 22 /// 规划了观察者的二种行为(方法),所有派生于该观察者基类的具体观察者都 23 /// 通过覆盖该方法来实现作出响应的行为. 24 /// </summary> 25 public abstract void Response(); 26 public abstract void Response2(); 27 }
3、建立具体的目标。
1 /**//// <summary> 2 /// 此类为观察者模式中的具体目标(即具体发布方),其继承于模型. 3 /// 其中包含(调用)了在模型中被封装好的触发委托事件的方法. 4 /// </summary> 5 public class Cat : ModelBase 6 { 7 public Cat() 8 { 9 } 10 /**//// <summary> 11 /// 定义了猫的一种行为----大叫 12 /// </summary> 13 public void Cry() 14 { 15 System.Console.WriteLine("Cat Cry.."); 16 //调用了触发委托事件的方法. 17 //通知委托开始执行观察者已订阅的方法. 18 this.Notify(); 19 } 20 }
4、建立具体的观察者。
4.1具体观察者1 老鼠
1 /**//// <summary> 2 /// 此类为观察者模式中的具体观察者(即具体发布方),其继承于观察者基类. 3 /// 其中覆盖了观察者基类规划好的方法,实现了响应的具体行为. 4 /// </summary> 5 public class Mouse : Observer 6 { 7 /**//// <summary> 8 /// 观察者可以拥有自己的成员(字段或者方法). 9 /// 在此事例中增加了"老鼠的名字" 10 /// </summary> 11 private string name; 12 /**//// <summary> 13 /// 构造时确定观察者所需要观察的对象(具体目标),并传递给观察者基类构造器, 14 /// 实现响应行为(方法)的订阅.另外,为观察者实例初始化成员. 15 /// </summary> 16 /// <param name="name">老鼠的名字</param> 17 /// <param name="childModel"> 18 /// 需要观察的对象(发布方). 19 /// 此处用模型基类来传递,是为了兼容所有派生于此模型的观察者,从而提高扩展性. 20 /// </param> 21 public Mouse(string name, ModelBase childModel) : base(childModel) 22 { 23 //初始化字段(老鼠的名字) 24 this.name=name; 25 } 26 /**//// <summary> 27 /// 覆盖了该类观察者需要作出的具体响应行为. 28 /// 此行为已在观察者基类中注册于委托事件,由委托事件调度执行,不需要直接调用. 29 /// </summary> 30 public override void Response() 31 { 32 //具体响应内容 33 System.Console.WriteLine(this.name+"开始逃跑"); 34 } 35 36 }
4.2建立具体观察者2 主人
1 /**//// <summary> 2 /// 此类为观察者模式中的具体观察者(即具体发布方),其继承于观察者基类. 3 /// 其中覆盖了观察者基类规划好的方法,实现了响应的具体行为. 4 /// </summary> 5 public class Master : Observer 6 { 7 /**//// <summary> 8 /// 构造时确定观察者所需要观察的对象(具体目标),并传递给观察者基类构造器, 9 /// 实现响应行为(方法)的订阅. 10 /// </summary> 11 public Master(ModelBase childModel) : base(childModel) 12 { 13 } 14 15 /**//// <summary> 16 /// 覆盖了该类观察者需要作出的具体响应行为. 17 /// 此行为已在观察者基类中注册于委托事件,由委托事件调度执行,不需要直接调用. 18 /// </summary> 19 public override void Response() 20 { 21 System.Console.WriteLine("主人醒来"); 22 } 23 }
4.3建立具体观察者3 孩子
1 /**//// <summary> 2 /// 此类为观察者模式中的具体观察者(即具体发布方),其继承了订阅了2个响应行为的 3 /// 观察者基类. 4 /// 其中覆盖了观察者基类规划好的二个方法,实现了响应的具体行为. 5 /// </summary> 6 public class Master2 : Observer2 7 { 8 /**//// <summary> 9 /// 构造时确定观察者所需要观察的对象(具体目标),并传递给观察者基类构造器, 10 /// 实现响应行为(方法)的订阅. 11 /// </summary> 12 public Master2(ModelBase childBase) : base(childBase) 13 { 14 } 15 16 /**//// <summary> 17 /// 覆盖了该类观察者需要作出的具体响应行为. 18 /// 此行为已在观察者基类中注册于委托事件,由委托事件调度执行,不需要直接调用. 19 /// </summary> 20 public override void Response() 21 { 22 Console.WriteLine("baby醒来。。。。"); 23 24 } 25 /**//// <summary> 26 /// 覆盖了该类观察者需要作出的另一个响应行为. 27 /// </summary> 28 public override void Response2() 29 { 30 Console.WriteLine("开始哭闹。。。。。"); 31 } 32 }
5、最终运行测试。 main函数代码。
1 static void Main(string[] args) 2 { 3 4 //声明并实例化一个目标(即发布方)对象----猫 5 Cat myCat = new Cat(); 6 //声明并实例化一个Mouse类型的观察者对象--名叫mouse1的老鼠.并把那只猫作为它所要观察的对象. 7 Mouse myMouse1 = new Mouse("mouse1", myCat); 8 //类似地生成另一只名叫mouse2的老鼠(观察者),把同一只猫作为它的观察的对象. 9 Mouse myMouse2 = new Mouse("mouse2", myCat); 10 //声明并实例化一个Master类型的观察者--主人,并同时把那只猫也作为他的观察对象. 11 Master myMaster = new Master(myCat); 12 //声明并实例化一个Master2类型的观察者--宝宝,同时把那只猫也 13 Master2 myLittleMaster = new Master2(myCat); 14 15 //猫大叫,并触发了委托事件,从而开始按顺序调用观察者已订阅的方法. 16 myCat.Cry(); 17 18 Console.Read(); 19 }
三、分析观察者模式所带来的好处。
观察者模式的效果有以下几个优点:
(1)由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。
(2)观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。观察者可以自动地获取到想要观察对象变化信息,用以做出响应和调整。
观察者模式有下面的一些缺点:
如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。
四、总结
实际上在C#中实现Observer模式没有这么辛苦,.NET中提供了Delegate与Event机制,我们可以利用这种机制简化Observer模式。关于Delegate与Event的使用方法请参考相关文档。就可以对Observer进行改进。
Observer模式的优点是实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,类别清晰,并抽象了更新接口,使得可以有各种各样不同的表示层(观察者)。
但是其缺点是每个外观对象必须继承这个抽像出来的接口类,这样就造成了一些不方便,比如有一个别人写的外观对象,并没有继承该抽象类,或者接口不对,我们又希望不修改该类直接使用它。会造成更加复杂烦琐的设计,增加出错几率。
五、参考资料
https://blog.csdn.net/cjolj/article/details/56482467?utm_source=blogxgwz17
http://www.cnblogs.com/zhenyulu/articles/73723.html
https://www.cnblogs.com/xmfdsh/p/4047114.html
六、代码GitHub地址
1.上述文章第一个例子地址:https://github.com/jaymayi/test/blob/master/Program.cs
2.上述文章实例观察者模式的例子地址:https://github.com/jaymayi/test/blob/master/Program2.cs
3.烧水例子参考:https://github.com/jaymayi/test/blob/master/Class1.cs