关于观察着设计模式的四种实现方式
观察着设计模式
对你没看错,这里有将近四中方式来事件我们的观察者模式,还有另外一种不太正规的实现方式;
经典案例:猫叫了,惊动老鼠 和 主人;
第一种利用面向对象的实现方式,分别使用接口和对接口的实现,也就是多肽的特性来进行拓展;
public interface Observer { void Response(); //观察者的响应,如是老鼠见到猫的反映 } public interface Subject { void Aimed(Observer obs); //针对哪些观察者 } public class Mouse : Observer { private string name; public Mouse(string name, Subject sub) { this.name = name; sub.Aimed(this); } public void Response() { Console.WriteLine(name+"attemp to escaped"); } } public class Master : Observer { public Master(Subject sub) { sub.Aimed(this); } public void Response() { Console.WriteLine("host waked"); } } public class Cat : Subject { private ArrayList objservers; public Cat() { this.objservers = new ArrayList(); } public void Aimed(Observer ob) { this.objservers.Add(ob); } public void Cry() { Console.WriteLine("Cat Cryed"); foreach (Observer o in this.objservers) { o.Response(); //接口的好处 ,面向我们的接口编程,具体的实现,交给我们的实际的实现类的方法滴哎呦; } } } public class Test { static void fuck() { Cat cat = new Cat(); Mouse m = new Mouse("nouse1",cat); Mouse m2 = new Mouse("nouse2", cat); Master ma = new Master(cat); cat.Cry(); } }
这个示例过于简单了一点,没有参数的出传递,也没有模拟临界条件去触发事件;
传递消息;
然后不同的对象做不同的反应; 这个反应我们可以抽象相同的方法名,那就是我们的Action 或 Response,效果还是挺好的;
这样的效果挺好的;
总之是感觉非常秒的代码滴呀;效果是非常好滴呀;
经过一段时间额学习之后,我又对代码尽心了相应的优化;
namespace ConsoleApplication92 { /// <summary> /// /// </summary> public interface Observer { /// <summary> /// 观察着应该做出的反应; /// </summary> /// <param name="sender"></param> /// <param name="args"></param> void Response(object sender, EventArgs args); /// <summary> /// 去订阅某个订阅对象;完善的做法,还有一些,取消订阅的方法和异常处理; /// </summary> void Subscribe(IList<Observer> observer); } /// <summary> /// 主题,出发出一个事件; /// </summary> public interface Subject { /// <summary> /// 敢兴趣的观察着们; /// </summary> IList<Observer> observers { get; set; } /// <summary> /// 某个事件 /// </summary> void SomeEvent(int sound); } public class CatEventArgs : EventArgs { /// <summary> /// 分贝 /// </summary> public int SoundDecibel { get; private set; } /// <summary> /// 参数 /// </summary> private CatEventArgs() { } /// <summary> /// 猫的参数 /// </summary> /// <param name="soundDecibel"></param> public CatEventArgs(int soundDecibel) { this.SoundDecibel = soundDecibel; } } /// <summary> /// /// </summary> public class Cat : Subject { public string Name { get; private set; } /// <summary> /// 初始化对象; /// </summary> /// <param name="name"></param> public Cat(string name) { this.Name = name; } /// <summary> /// 敢兴趣的观察着; /// </summary> public IList<Observer> observers { get; set; } public void Sleep() { Console.WriteLine(this.Name + " sleep....."); } /// <summary> /// 某个事件的发生; /// 事件的发生我们已经定义好了; /// </summary> public void SomeEvent(int sound) { //某个事情的发生 if (sound < 90) { Sleep(); } else { CatEventArgs agrs = new CatEventArgs(sound); Notify(agrs); } } /// <summary> /// 通知我们的额事件订阅者; /// </summary> /// <param name="args"></param> private void Notify(EventArgs args) { foreach (Observer o in this.observers) { o.Response(this, args); } } } public class Mouse : Observer { public string Name { get; private set; } /// <summary> /// 初始化对象; /// </summary> /// <param name="name"></param> public Mouse(string name) { Name = name; } /// <summary> /// /// </summary> /// <param name="sender"></param> /// <param name="args"></param> public void Response(object sender, EventArgs args) { var c = (Cat)sender; var arg = (CatEventArgs)args; Console.WriteLine(arg.SoundDecibel + "soud deciebel"); //声音额分贝 Console.WriteLine(c.Name + " weak up..."); //这里能够获得事件放生的对象和 相关参数; Console.WriteLine(this.Name + "mouse run away..."); } /// <summary> /// 去订阅我们的额某个subject /// </summary> public void Subscribe(IList<Observer> observer) { observer.Add(this); //把字节给添加进去; } } public class Client { /// <summary> /// /// </summary> public static void Test() { ///初始化我们的事件源对象; Cat c = new Cat("Tom"); c.observers = new List<Observer>(); Mouse m = new Mouse("jerry"); m.Subscribe(c.observers); //去订阅某个对象; c.SomeEvent(95); Console.ReadLine(); } } }
对于这种模式的抽象,net中已经给我们提供了较好的接口封装;
那就是我们的IObservable 和 IObserver
他们两分别等于我们的subject 和我们的observer
效果是非常好的;
满分;
然后,就以此打开我们基于事件编程的大门;
什么reactive 啊 消息队列啊,rabbitq啊 akka 等等的大门就开了; 这些东西,完全太吸引人了,完全就是一个新大陆滴呀;
简直就发现了新大陆;
完美
这里就是我们的代码示例:
//先封装我们的参数; 应为是我们的: Iobserver 模式,这里就不用继承自我们的: EventArgs public class CatEventArgs { /// <summary> /// 声音的分贝 /// </summary> public double SoundQuantity { get; internal set; } private CatEventArgs() { } public CatEventArgs(double soundQuantity) { SoundQuantity = soundQuantity; } } /// <summary> /// 销毁对象;也就是等于取消订阅; /// </summary> public class DisposedAction : IDisposable { private readonly Action _action; public DisposedAction(Action action) { _action = action; } public void Dispose() { _action(); //自动的销毁我们想要的基本对象; } } //这只猫需要对外公布两个方法,订阅和取消订阅 以及我们事件的出发点; public class Cat : IObservable<CatEventArgs> { private readonly List<IObserver<CatEventArgs>> _observers; public double CatSoundQuntity { get; internal set; } public Cat(double catSoundQuntity=0) { CatSoundQuntity = catSoundQuntity; _observers = new List<IObserver<CatEventArgs>>(); //容器的初始化; } //这里注册,添加,对应的订阅者; public IDisposable Subscribe(IObserver<CatEventArgs> observer) { if (!_observers.Contains(observer)) { _observers.Add(observer); } //这里还要去实现一个 IDisposable return new DisposedAction(() => _observers.Remove(observer)); //这里实现我们一个销毁对象的方法; //返回了取消对象的委托;窝草,这个方法好高级; } //触发实现的代码,不一定要封装在这里名,触发的事件也可以在外部调经引起的的; //这个看我们额事件清楚; public void OnTrigger(CatEventArgs e) { if (_observers.Any()) { _observers.ForEach(obj => obj.OnNext(e)); } } public void OnCry() { //事件触发点; for (var i = 0; i < 100; i++) //模拟声音的 { if (i >= 60) //大约六十分贝触发事件; { CatEventArgs e = new CatEventArgs(i); OnTrigger(e);//通知订阅者; return; //退出循环,避免连续触发事件; } } } } //用泛型,这里当然是为了避免我们的参数的装箱和拆箱了; public class Mouse : IObserver<CatEventArgs> { public string Name { get; private set; } private IDisposable _Subscriber; public Mouse(string name) { Name = name; } //当然还有我么额取消订阅; public void Subscribe(Cat c) { _Subscriber = c.Subscribe(this); //订阅我们的感兴趣的时间 } public void UnSubscribe() { _Subscriber.Dispose(); //这是一个好的方法; } //对应接口的实现,就在我们这里的拉滴呀; public void OnNext(CatEventArgs e) { Console.WriteLine($"猫的分贝大小是:{e.SoundQuantity}/所以惊动了老鼠"); } public void OnError(Exception error) { Console.WriteLine(error.Message); } public void OnCompleted() { Console.WriteLine("OnCompleted"); } } class Program { static void Test() { Cat cat = new Cat(); //完全可以将事件的出发点放在 类的外面; Mouse m = new Mouse("Jack"); m.Subscribe(cat); //订阅我们所感兴趣的事情; cat.OnCry(); //事件触发,这个完全暴露给外部来触发事件; } static void Main(string[] args) { Test(); Console.ReadLine(); } }
第二种模式是使用我们委托和事件的特性来进行拓展;
这种方式里面也包含了面向对象的特性滴呀;
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication4 { //这里是对应的我们的第二种实现的方式; public delegate void SubEventHanlder(); public abstract class Subject { public event SubEventHanlder subEvent; protected void FireAway() { if (this.subEvent != null) this.subEvent(); } } public class Cat : Subject { public void Cry() { Console.WriteLine("cat cryed"); this.FireAway(); } } public abstract class Observer { public Observer(Subject sub) { sub.subEvent += new SubEventHanlder(Response); } public abstract void Response(); } public class Mouse : Observer { private string name; public Mouse(string name, Subject sub) : base(sub) { this.name = name; } public override void Response() { Console.WriteLine(name + "attemp to escape!"); } } public class Master : Observer { public Master(Subject sub) : base(sub) { } public override void Response() { Console.WriteLine("host waked"); } } public class Test { public void t() { Cat c = new Cat(); Mouse m1 = new Mouse("m1", c); Mouse m2 = new Mouse("m2", c); Master m = new Master(c); c.Cry(); } } }
后面我们再看看以前,一篇关于烧开水的 观察模式额实例
那个实例也是比较经典滴呀;效果非常好滴呀;
好今天,我们就来复习这个精当的烧开的经典示例;
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication6 { //热水器 public class Heater { private int temperature; public delegate void BoilHandler(int param); public event BoilHandler BoilEvent; //烧水; public void BoilWater() { for(var i = 0; i <= 100; i++) { temperature = i; if (temperature==95) { if (BoilEvent != null) { BoilEvent(temperature); //通知所有对这个事件感兴趣的观察着; 其实这里还没考虑到事件的安全性低呀;效果就明显不一样了滴呀; } } } } } // 警报器 public class Alarm { public void MakeAlert(int param) { Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:", param); } } //显示器 public class Display { public static void ShowMsg(int param) { //静态方法 Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", param); } } //其他; public class OtherObserver { public static void FuckLife(int param) { Console.WriteLine("其他,待扩展额程序.....{0}", param); } } class Program { static void Main(string[] args) { Heater heater = new Heater(); Alarm alarm = new Alarm(); //在类的进行事件的注册; heater.BoilEvent+= alarm.MakeAlert; heater.BoilEvent += Display.ShowMsg; heater.BoilEvent += OtherObserver.FuckLife; // heater.BoilWater(); //烧水 Console.ReadLine(); } } }
//然后是我们 net 中事件;
Net Framework中的委托与事件
尽管上面的范例很好地完成了我们想要完成的工作,但是我们不仅疑惑:为什么.Net Framework 中的事件模型和上面的不同?为什么有很多的EventArgs参数?
在回答上面的问题之前,我们先搞懂 .Net Framework的编码规范:
- 委托类型的名称都应该以EventHandler结束。
- 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
- 事件的命名为 委托去掉 EventHandler之后剩余的部分。
- 继承自EventArgs的类型应该以EventArgs结尾。
再做一下说明:
- 委托声明原型中的Object类型的参数代表了Subject,也就是监视对象,在本例中是 Heater(热水器)。回调函数(比如Alarm的MakeAlert)可以通过它访问触发事件的对象(Heater)。
- EventArgs 对象包含了Observer所感兴趣的数据,在本例中是temperature。
最后的完整版代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication7 { //web developer information; //这里委托的原型定义:一个void返回值,并接受两个参数一个object类型的,一个eventArgs类型(继承自EventArgs) public class Heater { private int temperature; public string type = "007"; public string area = "chengdu"; public delegate void BoildEventHanlder(Object sender, BoiledEventArgs e); public event BoildEventHanlder Boiled; //定义一个boiledEventArg类,传递个感兴趣的observer对象; public class BoiledEventArgs : EventArgs { public readonly int temperature; public BoiledEventArgs(int temp) { this.temperature = temp; } } //提供一个可重写的,比变继承类可以拒绝其他对象对它的监视 protected virtual void OnBoiled(BoiledEventArgs e) { if (Boiled != null) { Boiled(this, e); // 调用所有注册对象的方法 } } // 烧水。 public void BoilWater() { for (int i = 0; i <= 100; i++) { temperature = i; if (temperature > 95) { //建立BoiledEventArgs 对象。 BoiledEventArgs e = new BoiledEventArgs(temperature); OnBoiled(e); // 调用 OnBolied方法 } } } } // 警报器 public class Alarm { public void MakeAlert(Object sender, Heater.BoiledEventArgs e) { Heater heater = (Heater)sender; //这里是不是很熟悉呢? //访问 sender 中的公共字段 Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type); Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", e.temperature); Console.WriteLine(); } } // 显示器 public class Display { public static void ShowMsg(Object sender, Heater.BoiledEventArgs e) { //静态方法 Heater heater = (Heater)sender; Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type); Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature); Console.WriteLine(); } } class Program { static void Main(string[] args) { //警报器和显示器的实现没有太大的变化; 主要是我们的额参数变化比较大 Heater h = new Heater(); Alarm alarm = new Alarm(); h.Boiled += alarm.MakeAlert; h.Boiled += Display.ShowMsg; h.BoilWater(); Console.ReadKey(); } } }
关于事件的使用,我们不得不考虑到内存泄漏的问题滴呀;
//然后这里我们再补充先关知识;
http://blog.csdn.net/hulihui/article/details/3217649
原文:
https://www.codeproject.com/Articles/29922/Weak-Events-in-C
然后,扩展学习,事件中的深入了解;
http://www.uml.org.cn/net/201303193.asp
常见的net中内存泄漏问题;