详解观察者模式

在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。

在软件世界也是这样,例如,Excel 中的数据与折线图、饼状图、柱状图之间的关系;MVC 模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。

使用场景

观察者模式所做的工作其实就是在解耦,让耦合的双方都依赖于抽象而不是具体,从而使得各自的变化都不会影响另一边的变化。

当一个对象的改变需要改变其他对象的时候,而且它不知道具体有多少对象有待改变的时候,应该考虑使用观察者模式。

一个抽象模型有两方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使得他们各自独立地改变和复用。

举例

public interface ISubject
{
   void Notify();

   string SubjectState { get; set; }
}
public class Boss : ISubject
{
   private readonly IList _observers = new List();

   public void Attach(Observer observer)
   {
       _observers.Add(observer);
   }

   public void Detach(Observer observer)
   {
       _observers.Remove(observer);
   }

   public void Notify()
   {
       foreach (var observer in _observers)
       {
           observer.Update();
       }
   }

   public string SubjectState { get; set; }
}

public abstract class Observer
{
   protected string Name;
   protected ISubject Subject;

   protected Observer(string name, ISubject subject)
   {
       Name = name;
       Subject = subject;
   }

   public abstract void Update();
}
public class StockObserver : Observer
{
   public StockObserver(string name, ISubject subject) : base(name, subject)
   {
   }

   public override void Update()
   {
       Console.WriteLine($"{Name} {Subject.SubjectState} 关闭股票行情,继续工作");
   }
}
public class NBAObserver : Observer
{
   public NBAObserver(string name, ISubject subject) : base(name, subject)
   {
   }

   public override void Update()
   {
       Console.WriteLine($"{Name} {Subject.SubjectState} 关闭 NBA 直播,继续工作");
   }
}


var boss = new Boss();
var stockObserver = new StockObserver("魏关姹", boss);
var nbaObserver = new NBAObserver("易管查", boss);

boss.Attach(stockObserver);
boss.Attach(nbaObserver);

boss.Detach(stockObserver);

boss.SubjectState = "老板我胡汉三回来了";
boss.Notify();

借助 event(委托) 我们可以实现可以灵活的观察者模式,我们定义了一个新老板来演示事件的方式,来看下面的示例:

public class NewBoss : ISubject
{
   public event Action Update;

   public void Notify()
   {
       Update?.Invoke();
   }

   public string SubjectState { get; set; }
}

public class GamePlayerObserver
{
   private readonly string _name;
   private readonly ISubject _subject;

   public GamePlayerObserver(string name, ISubject subject)
   {
       _name = name;
       _subject = subject;
   }

   public void CloseGame()
   {
       Console.WriteLine($"{_name} {_subject.SubjectState} 关闭 LOL 游戏,继续工作");
   }
}

var newBoss = new NewBoss();
var stockObserver = new StockObserver("魏关姹", boss);
var nbaObserver = new NBAObserver("易管查", boss);
var gameObserver = new GamePlayerObserver("西门", newBoss);

// 注册通知事件
newBoss.Update += stockObserver.Update;
newBoss.Update += nbaObserver.Update;
newBoss.Update += gameObserver.CloseGame;

newBoss.Update -= stockObserver.Update;

newBoss.SubjectState = "老板我胡汉三回来了";
newBoss.Notify();

从上面这个示例可以看到,通过事件的方式,我们可以不要求显示继承于 Observer 这个抽象类,可以更加灵活,扩展性更强,这也是很多类库中会使用事件来扩展的重要原因

More

设计模式要干的事情就是解耦。创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。

根据应用场景的不同,观察者模式会对应不同的代码实现方式:有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。

在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。

EventBus(事件总线) 就是一个观察者模式的实际应用。

原创文章,作者:晴川运维,如若转载,请注明出处:https://baike.qcidc.com/5739.html

(0)
晴川运维晴川运维
上一篇 2025年6月8日
下一篇 2025年6月8日

相关推荐

  • 企业的六种数据存储合规性策略

    强调合规性的数据存储管理人员应该遵循行业机构分析师推荐的策略,其中包括采用自动化技术和匿名数据。存储专业人士如今有很多事情要做,但在隐私法规范围不断扩展的时代,他们的任务清单却在不…

    Linux系统 2025年6月9日
  • Docker中安装Redmine具体步骤

    Redmine是一个开源的、基于Web的项目管理和缺陷跟踪工具。它用日历和甘特图辅助项目及进度可视化显示。同时它又支持多项目管理,下面为大家分享一下Docker中安装Redmine…

    Linux系统 2025年6月12日
  • Linux下crontab使用方法

    Linux crontab是用来定期执行程序的命令,当安装完成操作系统之后,默认便会启动此任务调度命令,crond 命令每分锺会定期检查是否有要执行的工作,如果有要执行的工作便会自…

    Linux系统 2025年6月8日
  • C语言结构体使用注意事项

    结构体是类似于名片形式的数据集合体,可以把它理解为一种由用户自定义的特殊的复合型的“数据类型”,在这个复合型的“数据类型”中可以包含多种基本数据类型,我们可以把它作为一个整体来操作…

    Linux系统 2025年7月9日
  • 使用unzip命令解压缩文件

    unzip解压命令的使用方法:【unzip test.zip】,表示将压缩文件test.zip解压到当前目录下。unzip命令用于解压缩由zip命令压缩的【.zip】压缩包。 安装…

    Linux系统 2025年10月27日
  • APF防火墙的安装和使用

    APF(Advanced Policy Firewall)是 Rf-x Networks 出品的Linux环境下的软件防火墙,被大部分Linux服务器管理员所采用,使用iptabl…

    Linux系统 2025年6月10日
  • DNS在什么场景下选择TCP与UDP协议

    DNS同时占用UDP和TCP端口53是公认的,这种单个应用协议同时使用两种传输协议的情况在TCP/IP栈也算是个另类。但很少有人知道DNS分别在什么情况下使用这两种协议。 TCP与…

    Linux系统 2025年6月8日
  • Linux C线程池的具体实现方法

    什么时候需要创建线程池呢?简单的说,如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了,下面为大…

    Linux系统 2025年6月27日
  • 在Django中使用ElasticSearch

    Elasticsearch可以使我们快速,近乎实时地存储,搜索和分析大量数据,并在几毫秒内给出答复。之所以能够获得快速的搜索响应,是因为它可以直接搜索索引,而不是直接搜索文本。 什…

    Linux系统 2025年6月8日
  • 通过Nginx反向代理实现kibana登录认证

    Kibana 5.5 版后,已不支持认证功能,也就是说,直接打开页面就能管理,想想都不安全,不过官方提供了 X-Pack 认证,但有时间限制。毕竟X-Pack是商业版。 下面我将操…

    Linux系统 2025年10月21日

发表回复

登录后才能评论