在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用。在这一章将分享我对观察者模式的理解,废话不多说了,直接进入今天的主题。 二、 观察者模式的介绍2.1 观察者模式的定义从生活中的例子可以看出,只要对订阅号进行关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的用户。从中,我们就可以得出观察者模式的定义。 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。 2.2 观察者模式的结构从上面观察者模式的定义和生活中的例子,很容易知道,观察者模式中首先会存在两个对象,一个是观察者对象,另一个就是主题对象,然而,根据面向接口编程的原则,则自然就有抽象主题角色和抽象观察者角色。理清楚了观察者模式中涉及的角色后,接下来就要理清他们之间的关联了,要想主题对象状态发生改变时,能通知到所有观察者角色,则自然主题角色必须所有观察者的引用,这样才能在自己状态改变时,通知到所有观察者。有了上面的分析,下面观察者的结构图也就很容易理解了。具体结构图如下所示:
图 观察者模式结构图 可以看出,在观察者模式的结构图有以下角色:
2.3 观察者模式的实现下面以微信订阅号的例子来说明观察者模式的实现。现在要实现监控腾讯游戏订阅号的状态的变化。这里一开始不采用观察者模式来实现,而通过一步步重构的方式,最终重构为观察者模式。因为一开始拿到需求,自然想到有两个类,一个是腾讯游戏订阅号类,另一个是订阅者类。订阅号类中必须引用一个订阅者对象,这样才能在订阅号状态改变时,调用这个订阅者对象的方法来通知到订阅者对象。有了这个分析,自然实现的代码如下所示: // 腾讯游戏订阅号类
public class TenxunGame
{
// 订阅者对象
public Subscriber Subscriber {get;set;}
public String Symbol {get; set;}
public string Info {get ;set;}
public void Update()
{
if (Subscriber != null)
{
// 调用订阅者对象来通知订阅者
Subscriber.ReceiveAndPrintData(this);
}
}
}
// 订阅者类
public class Subscriber
{
public string Name { get; set; }
public Subscriber(string name)
{
this.Name = name;
}
public void ReceiveAndPrintData(TenxunGame txGame)
{
Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, txGame.Symbol, txGame.Info);
}
}
// 客户端测试
class Program
{
static void Main(string[] args)
{
// 实例化订阅者和订阅号对象
Subscriber LearningHardSub = new Subscriber("LearningHard");
TenxunGame txGame = new TenxunGame();
txGame.Subscriber = LearningHardSub;
txGame.Symbol = "TenXun Game";
txGame.Info = "Have a new game published ....";
txGame.Update();
Console.ReadLine();
}
}
上面代码确实实现了监控订阅号的任务。但这里的实现存在下面几个问题:
上面的设计违背了“开放——封闭”原则,显然,这不是我们想要的。对此我们要做进一步的抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用它来取消TenxunGame类与具体的订阅者之间的依赖,做这样一步改进,确实可以解决TenxunGame类与具体订阅者之间的依赖,使其依赖与接口,从而形成弱引用关系,但还是不能解决出现一个订阅者不得不修改TenxunGame代码的问题。对此,我们可以做这样的思考——订阅号存在多个订阅者,我们可以采用一个列表来保存所有的订阅者对象,在订阅号内部再添加对该列表的操作,这样不就解决了出现新订阅者的问题了嘛。并且订阅号也属于变化的部分,所以,我们可以采用相同的方式对订阅号进行抽象,抽象出一个抽象的订阅号类,这样也就可以完美解决上面代码存在的问题了,具体的实现代码为: // 订阅号抽象类
public abstract class TenXun
{
// 保存订阅者列表
private List<IObserver> observers = new List<IObserver>();
public string Symbol { get; set; }
public string Info { get; set; }
public TenXun(string symbol, string info)
{
this.Symbol = symbol;
this.Info = info;
}
#region 新增对订阅号列表的维护操作
public void AddObserver(IObserver ob)
{
observers.Add(ob);
}
public void RemoveObserver(IObserver ob)
{
observers.Remove(ob);
}
#endregion
public void Update()
{
// 遍历订阅者列表进行通知
foreach (IObserver ob in observers)
{
if (ob != null)
{
ob.ReceiveAndPrint(this);
}
}
}
}
// 具体订阅号类
public class TenXunGame : TenXun
{
public TenXunGame(string symbol, string info) : base(symbol, info)
{
}
}
// 订阅者接口
public interface IObserver
{
void ReceiveAndPrint(TenXun tenxun);
}
// 具体的订阅者类
public class Subscriber : IObserver
{
public string Name { get; set; }
public Subscriber(string name)
{
this.Name = name;
}
public void ReceiveAndPrint(TenXun tenxun)
{
Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info);
}
}
// 客户端测试
class Program
{
static void Main(string[] args)
{
TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ....");
// 添加订阅者
tenXun.AddObserver(new Subscriber("Learning Hard"));
tenXun.AddObserver(new Subscriber("Tom"));
tenXun.Update();
Console.ReadLine();
}
}
上面代码是我们进行重构后的实现,重构后的代码实现类图如下所示:
从上图可以发现,这样的实现就是观察者模式的实现。这样,在任何时候,只要调用了TenXun类的Update方法,它就会通知所有的观察者对象,同时,可以看到,观察者模式,取消了直接依赖,变为间接依赖,这样大大提供了系统的可维护性和可扩展性。这里并不是直接给出观察者模式的实现,而是通过一步步重构的方式来引出观察者模式的实现,相信通过这个方式,大家可以更深刻地理解观察者模式所解决的问题和带来的好处。 三、.NET 中观察者模式的应用在.NET中,我们可以使用委托与事件来简化观察者模式的实现,上面的例子用事件和委托的实现如下代码所示: namespace ObserverInNET
{
class Program
{
// 委托充当订阅者接口类
public delegate void NotifyEventHandler(object sender);
// 抽象订阅号类
public class TenXun
{
public NotifyEventHandler NotifyEvent;
public string Symbol { get; set; }
public string Info { get; set; }
public TenXun(string symbol, string info)
{
this.Symbol = symbol;
this.Info = info;
}
#region 新增对订阅号列表的维护操作
public void AddObserver(NotifyEventHandler ob)
{
NotifyEvent += ob;
}
public void RemoveObserver(NotifyEventHandler ob)
{
NotifyEvent -= ob;
}
#endregion
public void Update()
{
if (NotifyEvent != null)
{
NotifyEvent(this);
}
}
}
// 具体订阅号类
public class TenXunGame : TenXun
{
public TenXunGame(string symbol, string info) : base(symbol, info)
{
}
}
// 具体订阅者类
public class Subscriber
{
public string Name { get; set; }
public Subscriber(string name)
{
this.Name = name;
}
public void ReceiveAndPrint(Object obj)
{
TenXun tenxun = obj as TenXun;
if (tenxun != null)
{
Console.WriteLine("Notified {0} of {1}'s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info);
}
}
}
static void Main(string[] args)
{
TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ....");
Subscriber lh = new Subscriber("Learning Hard");
Subscriber tom = new Subscriber("Tom");
// 添加订阅者
tenXun.AddObserver(new NotifyEventHandler(lh.ReceiveAndPrint));
tenXun.AddObserver(new NotifyEventHandler(tom.ReceiveAndPrint));
tenXun.Update();
Console.WriteLine("-----------------------------------");
Console.WriteLine("移除Tom订阅者");
tenXun.RemoveObserver(new NotifyEventHandler(tom.ReceiveAndPrint));
tenXun.Update();
Console.ReadLine();
}
}
}
从上面代码可以看出,使用事件和委托实现的观察者模式中,减少了订阅者接口类的定义,此时,.NET中的委托正式充到订阅者接口类的角色。使用委托和事件,确实简化了观察者模式的实现,减少了一个IObserver接口的定义,上面代码的运行结果如下图所示:
四、观察者模式的适用场景在下面的情况下可以考虑使用观察者模式:
五、观察者模式的优缺点观察者模式有以下几个优点:
观察者也存在以下一些缺点:
六 总结到这里,观察者模式的分享就介绍了。观察者模式定义了一种一对多的依赖关系,让多个观察者对象可以同时监听某一个主题对象,这个主题对象在发生状态变化时,会通知所有观察者对象,使它们能够自动更新自己,解决的是“当一个对象的改变需要同时改变多个其他对象”的问题。大家可以以微信订阅号的例子来理解观察者模式。
使用事件 EventHandler 实现观察者模式事件具有以下特点: namespace ConsoleApplication2 { using System; using System.Collections.Generic; // 自定义一个事件类来保存事件信息 public class CustomEventArgs : EventArgs { public CustomEventArgs(string s) { message = s; } private string message; public string Message { get { return message; } set { message = value; } } } // 广播事件的类 class Publisher { // 使用 EventHandler<T> 声明一个事件 public event EventHandler<CustomEventArgs> RaiseCustomEvent; //这个方法是做了一些事情。 然后触发一个事件。 public void DoSomething() { //DoSomething………… // 你也可以再触发事件前,执行一些其他的代码 OnRaiseCustomEvent(new CustomEventArgs("Did something,hi 这是事件消息")); } //使用虚方法,让子类可以重写。 to allow derived classes to override the event invocation behavior protected virtual void OnRaiseCustomEvent(CustomEventArgs e) { // 定义一个局部变量,已防止最后一个订阅者刚好在检查null后取消订阅 EventHandler<CustomEventArgs> handler = RaiseCustomEvent; // 如果没有 订阅者(观察者), 委托对象将为null if (handler != null) { // 格式化事件消息中的字符串 e.Message += String.Format(" at {0}", DateTime.Now.ToString()); // 这是最重要的一句。 // 此时执行的 handler已经是一个多播委托(如果有多个订阅者或观察者注册)。 // 既然是多播委托,就可以依次调用各个 回调函数 (既然是回调函数,实际的执行就由订阅者类决定)。 //这里面传入一个this, 就代表 事件源(或发布者 或 被观察者 都一个意思) handler(this, e); } } } //用来注册事件的类 class Subscriber { private string id; public Subscriber(string ID, Publisher pub) { id = ID; // 注册事件,使用C#2.0语法 //注册这个动作,应该有订阅者主动进行,并且可以再后期取消注册 pub.RaiseCustomEvent += HandleCustomEvent; } // 实现回调函数。在事件发生后,执行什么样的操作。这里只是简单的打印信息。 void HandleCustomEvent(object sender, CustomEventArgs e) { //这就是实际的操作。 Console.WriteLine(id + " received this message: {0}", e.Message); } } class Class1 { static void Main(string[] args) { Publisher pub = new Publisher(); Subscriber sub1 = new Subscriber("sub1", pub); Subscriber sub2 = new Subscriber("sub2", pub); // 调用这个方法来产生事件 pub.DoSomething(); // Keep the console window open Console.WriteLine("Press Enter to close this window."); Console.ReadLine(); } } }
用内置接口IObservable<T>和IObserver<T>实现观察者模式1. 博客文章 https://blog.csdn.net/fdc2017/article/details/80091219 2. 官网文档 https://docs.microsoft.com/zh-cn/dotnet/standard/events/observer-design-pattern-best-practices |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论