在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用。在这一章将分享我对观察者模式的理解,废话不多说了,直接进入今天的主题。
从生活中的例子可以看出,只要对订阅号进行关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的用户。从中,我们就可以得出观察者模式的定义。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。
从上面观察者模式的定义和生活中的例子,很容易知道,观察者模式中首先会存在两个对象,一个是观察者对象,另一个就是主题对象,然而,根据面向接口编程的原则,则自然就有抽象主题角色和抽象观察者角色。理清楚了观察者模式中涉及的角色后,接下来就要理清他们之间的关联了,要想主题对象状态发生改变时,能通知到所有观察者角色,则自然主题角色必须所有观察者的引用,这样才能在自己状态改变时,通知到所有观察者。有了上面的分析,下面观察者的结构图也就很容易理解了。具体结构图如下所示:
图 观察者模式结构图
可以看出,在观察者模式的结构图有以下角色:
下面以微信订阅号的例子来说明观察者模式的实现。现在要实现监控腾讯游戏订阅号的状态的变化。这里一开始不采用观察者模式来实现,而通过一步步重构的方式,最终重构为观察者模式。因为一开始拿到需求,自然想到有两个类,一个是腾讯游戏订阅号类,另一个是订阅者类。订阅号类中必须引用一个订阅者对象,这样才能在订阅号状态改变时,调用这个订阅者对象的方法来通知到订阅者对象。有了这个分析,自然实现的代码如下所示:
1 // 腾讯游戏订阅号类 2 public class TenxunGame 3 { 4 // 订阅者对象 5 public Subscriber Subscriber {get;set;} 6 7 public String Symbol {get; set;} 8 9 public string Info {get ;set;} 10 11 public void Update() 12 { 13 if (Subscriber != null) 14 { 15 // 调用订阅者对象来通知订阅者 16 Subscriber.ReceiveAndPrintData(this); 17 } 18 } 19 20 } 21 22 // 订阅者类 23 public class Subscriber 24 { 25 public string Name { get; set; } 26 public Subscriber(string name) 27 { 28 this.Name = name; 29 } 30 31 public void ReceiveAndPrintData(TenxunGame txGame) 32 { 33 Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, txGame.Symbol, txGame.Info); 34 } 35 } 36 37 // 客户端测试 38 class Program 39 { 40 static void Main(string[] args) 41 { 42 // 实例化订阅者和订阅号对象 43 Subscriber LearningHardSub = new Subscriber("LearningHard"); 44 TenxunGame txGame = new TenxunGame(); 45 46 txGame.Subscriber = LearningHardSub; 47 txGame.Symbol = "TenXun Game"; 48 txGame.Info = "Have a new game published ...."; 49 50 txGame.Update(); 51 52 Console.ReadLine(); 53 } 54 }
上面代码确实实现了监控订阅号的任务。但这里的实现存在下面几个问题:
上面的设计违背了“开放——封闭”原则,显然,这不是我们想要的。对此我们要做进一步的抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用它来取消TenxunGame类与具体的订阅者之间的依赖,做这样一步改进,确实可以解决TenxunGame类与具体订阅者之间的依赖,使其依赖与接口,从而形成弱引用关系,但还是不能解决出现一个订阅者不得不修改TenxunGame代码的问题。对此,我们可以做这样的思考——订阅号存在多个订阅者,我们可以采用一个列表来保存所有的订阅者对象,在订阅号内部再添加对该列表的操作,这样不就解决了出现新订阅者的问题了嘛。并且订阅号也属于变化的部分,所以,我们可以采用相同的方式对订阅号进行抽象,抽象出一个抽象的订阅号类,这样也就可以完美解决上面代码存在的问题了,具体的实现代码为:
1 // 订阅号抽象类 2 public abstract class TenXun 3 { 4 // 保存订阅者列表 5 private List<IObserver> observers = new List<IObserver>(); 6 7 public string Symbol { get; set; } 8 public string Info { get; set; } 9 public TenXun(string symbol, string info) 10 { 11 this.Symbol = symbol; 12 this.Info = info; 13 } 14 15 #region 新增对订阅号列表的维护操作 16 public void AddObserver(IObserver ob) 17 { 18 observers.Add(ob); 19 } 20 public void RemoveObserver(IObserver ob) 21 { 22 observers.Remove(ob); 23 } 24 #endregion 25 26 public void Update() 27 { 28 // 遍历订阅者列表进行通知 29 foreach (IObserver ob in observers) 30 { 31 if (ob != null) 32 { 33 ob.ReceiveAndPrint(this); 34 } 35 } 36 } 37 } 38 39 // 具体订阅号类 40 public class TenXunGame : TenXun 41 { 42 public TenXunGame(string symbol, string info) 43 : base(symbol, info) 44 { 45 } 46 } 47 48 // 订阅者接口 49 public interface IObserver 50 { 51 void ReceiveAndPrint(TenXun tenxun); 52 } 53 54 // 具体的订阅者类 55 public class Subscriber : IObserver 56 { 57 public string Name { get; set; } 58 public Subscriber(string name) 59 { 60 this.Name = name; 61 } 62 63 public void ReceiveAndPrint(TenXun tenxun) 64 { 65 Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info); 66 } 67 } 68 69 // 客户端测试 70 class Program 71 { 72 static void Main(string[] args) 73 { 74 TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ...."); 75 76 // 添加订阅者 77 tenXun.AddObserver(new Subscriber("Learning Hard")); 78 tenXun.AddObserver(new Subscriber("Tom")); 79 80 tenXun.Update(); 81 82 Console.ReadLine(); 83 } 84 }
上面代码是我们进行重构后的实现,重构后的代码实现类图如下所示:
从上图可以发现,这样的实现就是观察者模式的实现。这样,在任何时候,只要调用了TenXun类的Update方法,它就会通知所有的观察者对象,同时,可以看到,观察者模式,取消了直接依赖,变为间接依赖,这样大大提供了系统的可维护性和可扩展性。这里并不是直接给出观察者模式的实现,而是通过一步步重构的方式来引出观察者模式的实现,相信通过这个方式,大家可以更深刻地理解观察者模式所解决的问题和带来的好处。
在.NET中,我们可以使用委托与事件来简化观察者模式的实现,上面的例子用事件和委托的实现如下代码所示:
1 namespace ObserverInNET 2 { 3 class Program 4 { 5 // 委托充当订阅者接口类 6 public delegate void NotifyEventHandler(object sender); 7 8 // 抽象订阅号类 9 public class TenXun 10 { 11 public NotifyEventHandler NotifyEvent; 12 13 public string Symbol { get; set; } 14 public string Info { get; set; } 15 public TenXun(string symbol, string info) 16 { 17 this.Symbol = symbol; 18 this.Info = info; 19 } 20 21 #region 新增对订阅号列表的维护操作 22 public void AddObserver(NotifyEventHandler ob) 23 { 24 NotifyEvent += ob; 25 } 26 public void RemoveObserver(NotifyEventHandler ob) 27 { 28 NotifyEvent -= ob; 29 } 30 31 #endregion 32 33 public void Update() 34 { 35 if (NotifyEvent != null) 36 { 37 NotifyEvent(this); 38 } 39 } 40 } 41 42 // 具体订阅号类 43 public class TenXunGame : TenXun 44 { 45 public TenXunGame(string symbol, string info) 46 : base(symbol, info) 47 { 48 } 49 } 50 51 // 具体订阅者类 52 public class Subscriber 53 { 54 public string Name { get; set; } 55 public Subscriber(string name) 56 { 57 this.Name = name; 58 } 59 60 public void ReceiveAndPrint(Object obj) 61 { 62 TenXun tenxun = obj as TenXun; 63 64 if (tenxun != null) 65 { 66 Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info); 67 } 68 } 69 } 70 71 static void Main(string[] args) 72 { 73 TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ...."); 74 Subscriber lh = new Subscriber("Learning Hard"); 75 Subscriber tom = new Subscriber("Tom"); 76 77 // 添加订阅者 78 tenXun.AddObserver(new NotifyEventHandler(lh.ReceiveAndPrint)); 79 tenXun.AddObserver(new NotifyEventHandler(tom.ReceiveAndPrint)); 80 81 tenXun.Update(); 82 83 Console.WriteLine("-----------------------------------"); 84 Console.WriteLine("移除Tom订阅者"); 85 tenXun.RemoveObserver(new NotifyEventHandler(tom.ReceiveAndPrint)); 86 tenXun.Update(); 87 88 Console.ReadLine(); 89 } 90 } 91 }
从上面代码可以看出,使用事件和委托实现的观察者模式中,减少了订阅者接口类的定义,此时,.NET中的委托正式充到订阅者接口类的角色。使用委托和事件,确实简化了观察者模式的实现,减少了一个IObserver接口的定义,上面代码的运行结果如下图所示:
在下面的情况下可以考虑使用观察者模式:
观察者模式有以下几个优点:
观察者也存在以下一些缺点:
到这里,观察者模式的分享就介绍了。观察者模式定义了一种一对多的依赖关系,让多个观察者对象可以同时监听某一个主题对象,这个主题对象在发生状态变化时,会通知所有观察者对象,使它们能够自动更新自己,解决的是“当一个对象的改变需要同时改变多个其他对象”的问题。大家可以以微信订阅号的例子来理解观察者模式。