C#设计模式之观察者模式

前言

观察者(Observer)模式也称发布-订阅(Publish-Subscribe)模式,定义了对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式的图解如下所示:

image-20240104152025996

Subject(目标):

目标知道它的观察者。可以有任意多个观察者观察同一个目标。

目标提供了注册和删除观察者对象的接口。

Observer(观察者):

为那些在目标发生改变时需获得通知的对象定义一个更新接口。

ConcreteSubject(具体目标):

将有关状态存入各ConcreteObserver对象。

当它的状态发生改变时,向它的各个观察者发出通知。

ConcreteObserver(具体观察者):

维护一个指向ConcreteSubject对象的引用。

存储有关状态,这些状态应与目标的状态保存一致。

实现Observer的更新接口以使自身状态与目标的状态保持一致。

不使用事件的示例

接下来我将根据上面的图解写一个C#观察者模式的例子,刚开始这个例子没有使用事件,在后面一个例子中使用事件。

首先来看看Subject(目标):

 //主题接口
 public interface ISubject
 {
     public void Attach(Observer observer);
     public void Detach(Observer observer);
     public void Notify();
     public string? SubjectState { get; set; }

 }

这里我使用的是接口,里面有Attach、Detach和Notify方法的声明,还有SubjectState属性的声明。

接下来看看ConcreteSubject(具体目标):

 public class School : ISubject
 {
     private IList<Observer> observers = new List<Observer>();
     public string? SubjectState { get; set; }
     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();
         }
     }
 }

这里我以学校发通知为例,School类实现了ISubject接口,有一个观察者的列表,在Attach方法中添加一个观察者,在Detach方法中移除一个观察者。在Notify方法中遍历观察者的列表,让每一个观察者都执行Update方法。

现在来看看Observer(观察者):

 public abstract class Observer
 {
     protected string? name;
     protected ISubject? subject;
     public Observer(string name,ISubject subject)
     {
         this.name = name;
         this.subject = subject;
     }
     public abstract void Update();
 }

这是一个抽象类,包含有一个抽象的Update方法。

再看看ConcreteObserver(具体观察者):

 public class Student : Observer
 {
     public Student(string name,ISubject subject) : base(name,subject) 
     { 

     }
     public override void Update() 
     {
         Console.WriteLine($"{name}接收到来自学校的通知:{subject?.SubjectState},
                           时间:{DateTime.Now}\r\n");
     }
 }

这里我以学生接收来自学校的消息为例,Student类继承自Observer抽象类,并重写了Update方法。

最后来看看怎么使用观察者模式:

 static void Main(string[] args)
 {
     School school = new School();
     Student student1 = new Student("小王", school);
     Student student2 = new Student("小明", school);
     Student student3 = new Student("小红", school);

     school.Attach(student1);
     school.Attach(student2);
     school.Attach(student3);

     school.SubjectState = "学校放假了";
     school.Notify();
    
     school.Detach(student3);

     Task.Delay(3000).Wait();

     school.SubjectState = "学校开学了";
     school.Notify();
     Console.ReadLine();

 }

创建一个School对象,三个Student对象。

     school.Attach(student1);
     school.Attach(student2);
     school.Attach(student3);

表示将student1、student2、student3添加到school中的观察者列表中。

 school.SubjectState = "学校放假了";
 school.Notify();

设置school中SubjectState属性的值,然后调用school的Notify方法。

 school.Detach(student3);

将student3从school的观察者列表中移除。

Task.Delay(3000).Wait();

等待3秒。

 school.SubjectState = "学校开学了";
 school.Notify();
 Console.ReadLine();

重新设置school中SubjectState属性的值,然后再调用school的Notify方法。

运行结果如下所示:

image-20240104183402030

学校放假了,小王、小明和小红都接收到了学校的通知。由于后面小红被移出了观察者列表,因此学校开学了的消息小红没有接收到。

使用事件的示例

C#中可以通过事件来使用观察者模式,接下来我将以一个示例加以说明。

自定义事件数据类:

 public class SendMessageArgs : EventArgs
 {
     public string? Message { get; set; }
     public DateTime DateTime { get; set; }
     public SendMessageArgs(string? message)
     {
         Message = message;
         DateTime = DateTime.Now;
     }
 }

Person类:

 public class Person
 {
     public string? Name { get; set; }
     public event EventHandler<SendMessageArgs>? SendMessageEvent;
     public Person(string? name)
     {
         Name = name;
     }
     public void SendMessage(string message) 
     { 
         SendMessageArgs sendMessageArgs = new SendMessageArgs(message); 
         
         SendMessageEvent?.Invoke(this, sendMessageArgs);
     }
     public void ShowMessage(object? sender,SendMessageArgs e)
     {
         Person? person = (Person?)sender;
         Console.WriteLine($"{this.Name}:收到来自{person?.Name}的消息:{e.Message},时间:{e.DateTime}\r\n");
     }
     public void Subscribe(Person person)
     {
         person.SendMessageEvent += ShowMessage;
     }
     public void UnSubscribe(Person person)
     {
         person.SendMessageEvent -= ShowMessage;
     }
 }

Person类中的SendMessage方法会触发事件,ShowMessage方法是事件处理程序,Subscribe方法可以订阅事件,UnSubscribe方法可以取消订阅事件。

现在来看看是怎么使用:

static void Main(string[] args)
{
    Person Trump = new Person("川普");
    Person Biden = new Person("拜登");

    Person person1 = new Person("person1");
    Person person2 = new Person("person2");
    Person person3 = new Person("person3");

    person1.Subscribe(Trump);
    person2.Subscribe(Trump);
    person3.Subscribe(Trump);

    person1.Subscribe(Biden);

    Trump.SendMessage("Nobody knows ... better than me!!!");

    Task.Delay(2000).Wait();

    Biden.SendMessage("I don't believe it!!!");

    person3.UnSubscribe(Trump);

    Task.Delay(2000).Wait();

    Trump.SendMessage("Make ... Great Again!!!");
    Console.ReadLine();          
}

创建了5个Person对象,分别为Trump、Biden、person1、person2、person3。

person1、person2、person3订阅了Trump,person1订阅了Biden。

Trump发了一条消息,然后过了2秒,Biden也发了一条消息。

person3不再订阅Trump,过了2秒,Trump又发消息了。

运行结果如下所示:

image-20240104184816290

由于person1、person2、person3订阅了Trump,所以可以收到来自Trump的消息。

由于person1订阅了Biden,所以可以收到来自Biden的消息。

后面person3退订了Trump,所以只有person1、person2能收到来自Trump的消息。

总结

以上使用C#分别创建了不通过事件与通过事件的示例,介绍了在C#中如何使用观察者模式,希望对你有所帮助。

参考

1、《Head First 设计模式(中文版)》

2、《大话设计模式》

3、《设计模式:可复用面向对象软件的基础》

4、YouTube [Design Patterns: C# Pub-Sub Simple Twitter example]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/293479.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 Kafka 和 CDC 将数据从 MongoDB Atlas 流式传输到 SingleStore Kai

SingleStore 提供了变更数据捕获 (CDC) 解决方案&#xff0c;可将数据从 MongoDB 流式传输到 SingleStore Kai。在本文中&#xff0c;我们将了解如何将 Apache Kafka 代理连接到 MongoDB Atlas&#xff0c;然后使用 CDC 解决方案将数据从 MongoDB Atlas 流式传输到 SingleStore…

JAVA基础学习笔记-day13-数据结构与集合源1

JAVA基础学习笔记-day13-数据结构与集合源1 1. 数据结构剖析1.1 研究对象一&#xff1a;数据间逻辑关系1.2 研究对象二&#xff1a;数据的存储结构&#xff08;或物理结构&#xff09;1.3 研究对象三&#xff1a;运算结构1.4 小结 2. 一维数组2.1 数组的特点 3. 链表3.1 链表的…

Linux之IP地址、主机名、域名解析

一、IP地址 可以通过ifconfig命令查看本机的ip地址&#xff0c;如果无法使用ifconfig命令&#xff0c;可以安装 安装&#xff1a;yum -y install net-tools ens33&#xff1a;主网卡&#xff0c;里面的inet就是ip地址 lo&#xff1a;本地回环网卡&#xff0c;127.0.0.1&…

Pytorch从零开始实战15

Pytorch从零开始实战——ResNeXt-50算法实战 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——ResNeXt-50算法实战环境准备数据集模型选择开始训练可视化总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.8&#xff0c;Pytor…

【计算机毕业设计】SSM医药信息管理系统

项目介绍 该系统共七个功能模块&#xff1a;查询模块、录入模块、删除模块、修改模块、浏览模块、打印模块和用户管理模块。 系统只有一个超级管理员&#xff0c;可以创建系统用户并进行权限管理&#xff0c;其他用户没有用户管理权限&#xff0c;只有其他权限。 不同的用户…

Jvm垃圾收集器系列之Parallel Scavenge收集器(个人见解仅供参考)

问&#xff1a;什么是Parallel Scavenge&#xff1f; 答&#xff1a;Parallel Scavenge是Java HotSpot虚拟机中的一种垃圾收集器&#xff0c;它主要用于提高应用程序的吞吐量。 问&#xff1a;Parallel Scavenge的主要目标是什么&#xff1f; 答&#xff1a;Parallel Scavenge的…

Debian12使用Xshell连接失败解决办法详细

1、Debian开启ssh服务 sudo apt update -y sudo apt install ssh2、编辑配置文件 # 安装vim sudo apt install vimvim /etc/ssh/sshd_config3、将#PermitRootLogin prohibit-password的注释去掉&#xff0c;设置为yes 4、将#PasswordAuthentication no的注释去掉&#xff0c;…

什么是DigiCert证书?

DigiCert作为全球知名的证书颁发机构&#xff0c;以其卓越的品质和全面的服务&#xff0c;为用户的数据安全保驾护航。 一、为何选择DigiCert证书&#xff1f; 权威认证&#xff1a;DigiCert与全球众多知名企业和政府机构合作&#xff0c;拥有广泛的认可度。高安全性&#xff…

太阳能杀虫灯的优点是什么

太阳能杀虫灯的优点主要包括以下几点&#xff1a; 环保节能&#xff1a;太阳能杀虫灯利用太阳能进行供电&#xff0c;无需接通市电&#xff0c;既节约能源又避免了排放污染物。适用范围广&#xff1a;只要有阳光照射的地区都可以使用太阳能杀虫灯&#xff0c;特别适合在电力资…

62.状态机实践(活动管理系统:二)

文章目录 一、简介二、状态机实践&#xff08;活动元信息管理&#xff09;1、dal/db.go2、dal/activity.go3、constdef/activity.go4、service/activity.go5、routes/routes.go6、main.go 代码地址&#xff1a;https://gitee.com/lymgoforIT/golang-trick/tree/master/37-load-…

详细解读QLC SSD无效编程问题-4

对于这些全部页面被无效化的WL&#xff0c;执行第二次编程实际上是不必要的&#xff0c;但当前的策略并未注意到这一问题。而对于那些既有有效页面又有无效页面&#xff08;图11中显示为1到3个&#xff09;的WL&#xff0c;应当被编程&#xff0c;但可以利用这些无效信息来改进…

C++设计模式 #8 抽象工厂(Abstract Factory)

抽象工厂这个名字比较难以帮助理解&#xff0c;可以把抽象工厂理解为“品牌工厂”或者“家族工厂”。 动机 在软件系统中&#xff0c;经常面临着“一系列相互依赖的对象”的创建工作&#xff1b;同时&#xff0c;由于需求的变化&#xff0c;往往存在更多系列对象的创建工作。如…

【Python可视化实战】钻石数据可视化

一、项目引言 1.背景和目标 钻石作为一种珍贵的宝石&#xff0c;其价格受到多种因素的影响。为了深入了解钻石价格的决定因素&#xff0c;我们收集了大量关于钻石的数据&#xff0c;并希望通过数据可视化来揭示钻石特征与价格之间的关系。 2.内容 收集钻石的各项特征数据&a…

【python高级用法】进程

一个简单的进程 # -*- coding: utf-8 -*-import multiprocessingdef foo(i):print (called function in process: %s %i)returnif __name__ __main__:Process_jobs []for i in range(5):p multiprocessing.Process(targetfoo, args(i,))Process_jobs.append(p)p.start()p.j…

Vue中的过滤器详解(应用场景和原理分析)

文章目录 一、是什么二、如何用定义filter小结&#xff1a; 三、应用场景四、原理分析小结&#xff1a; 参考文献 一、是什么 过滤器&#xff08;filter&#xff09;是输送介质管道上不可缺少的一种装置 大白话&#xff0c;就是把一些不必要的东西过滤掉 过滤器实质不改变原…

K-最近邻算法(KNN)是什么算法?

K-最近邻算法&#xff08;K-Nearest Neighbor&#xff0c;KNN&#xff09;是一种经典的有监督学习方法&#xff0c;也可以被归为懒惰学习&#xff08;Lazy Learning&#xff09;方法。它基于“物以类聚”的原理&#xff0c;假设样本之间的类别距离越近则它们越有可能是同一类别…

关于目标检测任务中,XML(voc格式)标注文件的可视化

1. 前言 最近在弄关于目标检测的任务&#xff0c;因为检测的图片和标签是分开的&#xff0c;可视化效果不明显&#xff0c;也不知道随便下载的数据集&#xff0c;标注信息对不对。网上看了好多代码&#xff0c;代码风格和本人平时不同&#xff0c;看起来麻烦&#xff0c;也不知…

项目使用PowerJob

新一代的定时任务框架——PowerJob 简介 PowerJob是基于java开发的企业级的分布式任务调度平台&#xff0c;与xxl-job一样&#xff0c;基于web页面实现任务调度配置与记录&#xff0c;使用简单&#xff0c;上手快速&#xff0c;其主要功能特性如下&#xff1a; 使用简单&…

ClickHouse基础介绍

目录 前言 1、什么是clickhouse 2、OLAP场景的关键特征 3、列式存储更适合于OLAP场景的原因 4、clickhouse的独特功能 5、clickhouse的缺点 6、性能 6.1、单个大查询的吞吐量 6.2、处理短查询的延迟时间 6.3、处理大量短查询的吞吐量 6.4、数据的写入性能 前言 11月…

RTSP/Onvif安防平台EasyNVR接入EasyNVS显示服务不存在的原因及解决办法

EasyNVS云管理平台具备汇聚与管理EasyGBS、EasyNVR等平台的能力&#xff0c;可以将接入的视频资源实现统一的视频能力输出&#xff0c;支持远程可视化运维等管理功能&#xff0c;还能解决设备现场没有固定公网IP却需要在公网直播的需求。 有用户在现场部署EasyNVR&#xff0c;…