设计模式—观察者模式
前言
在生活中有很多观察者模式的例子,比如微信中的订阅号,订阅博客,qq微博中关注好友,都属于观察者模式
观察者模式介绍
观察者模式定义了一种一对多的依赖关系,让多个观察者欧诺个时监听某一个主题对象,这个主题对象在发生变化时,会通知所有观察者对象,使他们能够自动更新自己的行为
1)实际例子
下面将会一步步的实现观察者模式是怎么封装的。现在要实现腾讯游戏订阅号的状态变化,腾讯游戏有更新时通知所有腾讯游戏的订阅者自动更新自身。首先要有两个类,腾讯游戏订阅号类和订阅者类。订阅号类中要引用一个订阅者对象,这样才能在订阅号发生变化时通知订阅者
第一版:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ConsoleApplication1 7 { /// <summary> 8 /// 腾讯游戏订阅号类 9 /// </summary> 10 public class TenxunGame 11 { 12 //订阅者对象 13 public Subscriber Subscriber { set; get; } 14 public string Symbol { set; get; } 15 public string Info { set; get; } 16 17 //调用订阅者对象来通知订阅者 18 public void Update() 19 { 20 if (Subscriber != null) 21 Subscriber.ReceiveAndPrintData(this); 22 } 23 } 24 25 /// <summary> 26 /// 订阅者类 27 /// </summary> 28 public class Subscriber 29 { 30 public string Name { set; get; } 31 public Subscriber(string name) 32 { 33 this.Name = name; 34 } 35 public void ReceiveAndPrintData(TenxunGame txGame) 36 { 37 Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, txGame.Symbol, txGame.Info); 38 } 39 } 40 }
客户端调用方法:
1 public static void test1() 2 { 3 Subscriber LearningHardSub = new Subscriber("LearningHard"); 4 TenxunGame txGame = new TenxunGame(); 5 txGame.Subscriber = LearningHardSub; 6 txGame.Symbol = "TenXun Game"; 7 txGame.Info = "Have a new game published..."; 8 9 txGame.Update(); 10 Console.ReadLine(); 11 }
运行结果:
第二版:
以上代码存在两个问题,
1.TenxunGame类和Subscriber类之间形成了双向依赖的关系,如果有其中一个类发生变化将引起另一个类的变化
2.当出现一个新的订阅者是,此时必须修改TenxunGame代码,即添加另一个订阅者的引用和在update方法中调用另一个订阅者的方法
对此我们需要做进一步的抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用他来取消TenxunGam类和具体订阅者的依赖。但还是不能解决出现一个订阅者不得不修改TenxunGame代码的问题。对此,我们可以做这样的思考——订阅号存在多个订阅者,我们可以采用一个列表来保存所有的订阅者对象,在订阅号内部再添加对该列表的操作,这样不就解决了出现新订阅者的问题了嘛。并且订阅号也属于变化的部分,所以,我们可以采用相同的方式对订阅号进行抽象,抽象出一个抽象的订阅号类
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ConsoleApplication2 7 { 8 /// <summary> 9 /// 订阅号抽象类 10 /// </summary> 11 public abstract class TenXun 12 { 13 //保存订阅者列表 14 private List<IObserver> observer = new List<IObserver>(); 15 16 public string Symbol { set; get; } 17 public string Info { set; get; } 18 public TenXun(string symbol, string info) 19 { 20 this.Symbol = symbol; 21 this.Info = info; 22 } 23 24 #region 对订阅者列表维护 25 public void AddObserver(IObserver ob) 26 { 27 observer.Add(ob); 28 } 29 30 public void RemoveObserver(IObserver ob) 31 { 32 observer.Remove(ob); 33 } 34 #endregion 35 36 //遍历订阅者列表进行通知 37 public void Update() 38 { 39 foreach (IObserver ob in observer) 40 { 41 if (ob != null) 42 ob.ReceiveAndPrint(this); 43 } 44 } 45 } 46 /// <summary> 47 /// 具体订阅号类 48 /// </summary> 49 public class TenXunGame : TenXun 50 { 51 public TenXunGame(string symbol, string info) 52 : base(symbol, info) 53 { 54 55 } 56 } 57 58 59 /// <summary> 60 /// 订阅者接口 61 /// </summary> 62 public interface IObserver 63 { 64 void ReceiveAndPrint(TenXun tenxun); 65 } 66 /// <summary> 67 /// 具体订阅者类 68 /// </summary> 69 public class Subserver : IObserver 70 { 71 public string Name { set; get; } 72 public Subserver(string name) 73 { 74 this.Name = name; 75 } 76 public void ReceiveAndPrint(TenXun tx) 77 { 78 Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tx.Symbol, tx.Info); 79 } 80 } 81 }
调用方法:
1 public static void test2() 2 { 3 ConsoleApplication2.TenXun tenXun = new ConsoleApplication2.TenXunGame("TenXun Game", "Have a new game published..."); 4 tenXun.AddObserver(new ConsoleApplication2.Subserver("LearningHard")); 5 tenXun.AddObserver(new ConsoleApplication2.Subserver("Tom")); 6 7 tenXun.Update(); 8 Console.ReadLine(); 9 }
运行结果:
2)观察者模式介绍
观察者模式中首先会存在两个对象,一个是观察者对象一个是主题对象。在面向对象中自然就有抽象观察者角色和抽象主题角色。要想主题对象在发生变化时,能够通知到所以的观察者对象,那么主题对象角色就必须有所有观察者的引用,这样才能在自己状态变化时通知所有观察者。
抽象主题角色:把所有观察者对象的引用保存在一个列表中,并提交增加好删除观察者对象的功能,抽象主题角色又叫做抽象被观察者角色,一般有抽象类或接口实现
抽象观察者角色:为所有具体观察者定义一个接口,在得到通知时更新自己,一般有抽象类或接口实现
具体主题角色:实现抽象主题接口
具体观察者角色:实现抽象观察者角色所要求的接口,使自己状态与主题状态想协调
3)观察者模式应用
在.net中我们可以使用委托与实际建行观察者模式的实现
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace ConsoleApplication3 7 { 8 public delegate void NotifyEventHandler(object sender); 9 10 /// <summary> 11 /// 订阅号抽象类 12 /// </summary> 13 public class TenXun 14 { 15 public NotifyEventHandler notifyEvent; 16 17 public string Symbol { set; get; } 18 public string Info { set; get; } 19 public TenXun(string symbol, string info) 20 { 21 this.Symbol = symbol; 22 this.Info = info; 23 } 24 25 #region 对订阅者列表维护 26 public void AddObserver(NotifyEventHandler ob) 27 { 28 notifyEvent += ob; 29 } 30 31 public void RemoveObserver(NotifyEventHandler ob) 32 { 33 notifyEvent -= ob; 34 } 35 #endregion 36 37 //遍历订阅者列表进行通知 38 public void Update() 39 { 40 if (notifyEvent != null) 41 notifyEvent(this); 42 43 } 44 } 45 46 public class TenXunGame : TenXun 47 { 48 public TenXunGame(string symbol, string info) : base(symbol, info) { } 49 } 50 51 public class Subscriber 52 { 53 public string Name { set; get; } 54 public Subscriber(string name) 55 { 56 this.Name = name; 57 } 58 public void ReceiveAndPrint(object obj) 59 { 60 TenXun tx = obj as TenXun; 61 if (tx != null) 62 Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tx.Symbol, tx.Info); 63 } 64 } 65 66 }
调用方法:
1 public static void test3() 2 { 3 ConsoleApplication3.TenXun tenXun = new ConsoleApplication3.TenXunGame("TenXun Game", "Have a new game published ...."); 4 ConsoleApplication3.Subscriber lh = new ConsoleApplication3.Subscriber("Learning Hard"); 5 ConsoleApplication3.Subscriber tom = new ConsoleApplication3.Subscriber("Tom"); 6 7 8 // 添加订阅者 9 tenXun.AddObserver(new ConsoleApplication3.NotifyEventHandler(lh.ReceiveAndPrint)); 10 tenXun.AddObserver(new ConsoleApplication3.NotifyEventHandler(tom.ReceiveAndPrint)); 11 12 tenXun.Update(); 13 14 Console.WriteLine("-----------------------------------"); 15 Console.WriteLine("移除Tom订阅者"); 16 tenXun.RemoveObserver(new ConsoleApplication3.NotifyEventHandler(tom.ReceiveAndPrint)); 17 tenXun.Update(); 18 19 Console.ReadLine(); 20 }
运行结果:
4)观察者模式分析
优点:
观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。
缺点:
如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。
5)使用场景
当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使它们可以各自独立地改变和复用的情况下。
当对一个对象的改变需要同时改变其他对象,而又不知道具体有多少对象有待改变的情况下。
当一个对象必须通知其他对象,而又不能假定其他对象是谁的情况下。