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]

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小程序测试和APP测试的区别

今天看了一下关于如何测试小程序的教学视频&#xff0c;里面讨论了一个很经典的面试题&#xff1a;小程序测试和APP测试的区别&#xff0c;包括在之前的面试过程中也确实是遇到过这个问题&#xff0c;所以这次打算把它记录下来&#xff0c;也算是知识巩固了。 首先从测试的内容…

android7以上 代码安装APK

一、所需权限 <!--请求安装APK的权限--> <uses-permission android:name"android.permission.REQUEST_INSTALL_PACKAGES" /> <!--写如外部存储的权限--> <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE"…

【DevOps-03】Build阶段-Maven安装配置

一、简要说明 下载安装JDK8下载安装Maven二、复制准备一台虚拟机 1、VM虚拟复制克隆一台机器 2、启动刚克隆的虚拟机,修改IP地址 刚刚克隆的虚拟机 ,IP地址和原虚拟的IP地址是一样的,需要修改克隆后的虚拟机IP地址,以免IP地址冲突。 # 编辑修改IP地址 $ vi /etc/sysconfig…

内存管理的概念-第四十一天

目录 前言 内存空间的分配与回收 内存空间的扩展 地址转换 存储保护 上下限寄存器 重定位寄存器和界地址寄存器 本节思维导图 前言 操作系统作为系统资源的管理者&#xff0c;当然也需要对内存进行管理&#xff0c;要管理什么呢&#xff1f; 操作系统复杂内存空间的分…

Lazada商品详情API(lazada.item_get)进行商品的实时更新

一、引言 在数字时代&#xff0c;电商平台如Lazada成为了商品交易的重要场所。为了保持竞争力&#xff0c;实时更新商品信息变得至关重要。Lazada提供的商品详情API&#xff08;lazada.item_get&#xff09;为开发者提供了一个高效的方式来获取并更新商品数据。本文将深入探讨…

SpringBoot全局Controller返回值格式统一处理

一、Controller返回值格式统一 1、WebResult类 在 Controller对外提供服务的时候&#xff0c;我们都需要统一返回值格式。一般定义一个 WebResult类。 统一返回值&#xff08;WebResult类&#xff09;格式如下&#xff1a; {"success": true,"code": 2…

express+mongoDB开发入门教程之mongoose使用讲解

系列文章 node.js express框架开发入门教程 expressmongoDB开发入门教程之mongoDB安装expressmongoDB开发入门教程之mongoose使用讲解 文章目录 系列文章前言一、Mongoose是什么&#xff1f;二、Mongoose安装三、Mongoose在express项目中使用步骤一、连接mongoDB数据库步骤二、…

模拟器怎么代理IP?代理IP对手机设置模拟器有哪些影响?

一、代理IP的基本概念和作用流冠代理IP是一种网络服务&#xff0c;可以帮助用户隐藏自己的真实IP地址&#xff0c;通过代理服务器进行网络请求&#xff0c;从而保护用户的隐私和安全。在模拟器中&#xff0c;代理IP的作用也是如此&#xff0c;可以帮助模拟器隐藏真实的IP地址&a…

javascript 常见工具函数(一)

1.将JSON数据根据相同值&#xff0c;进行归类划分&#xff1a; var arr [{ time: "1", img: "22222" }, { time: "2", img: "555" }, { time: "1", img: "888888" }, { time: "2", img: "4444&q…

MySQL Too many connections报错

MySQL 时不时出现Too many connections报错&#xff0c;重启MySQL就好了 但是过段时间又出现 一、解决方案&#xff1a; 1.修改mysql最大连接数 set global max_connections500; 以上是修改立即生效的&#xff0c;重启MySQL就会还原回去 在MySQL配置文件修改 max_connection…

力扣刷题-二叉树-二叉搜索树中的搜索

700 二叉搜索树中的搜索 给定二叉搜索树&#xff08;BST&#xff09;的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 NULL。 例如&#xff0c; 在上述示例中&#xff0c;如果要找的值是 5&#x…

UDP单播

CMakeLists.txt文件中添加如下行&#xff1a; link_libraries(ws2_32) 1.发送端 #include <iostream> #include <winsock2.h> #include <cstdio>#pragma comment(lib, "Ws2_32.lib") // Link with ws2_32.libint main() {1.Initialize winsock…

JS 手写 new 函数

工作中我们经常会用到 new 关键字&#xff0c;new 一个构造函数生成一个实例对象&#xff0c;那么new的过程中发生了什么呢&#xff0c;我们今天梳理下 创建一个对象对象原型继承绑定函数this返回对象 先创建一个构造函数&#xff0c;原型上添加一个方法 let Foo function (n…

03、Kafka ------ CMAK(Kafka 图形界面管理工具) 下载、安装、启动

目录 CMAK&#xff08;Kafka 图形界面管理工具&#xff09;下载安装启动打开 cmak 图形界面 CMAK&#xff08;Kafka 图形界面管理工具&#xff09; Kafka本身并没有提供Web管理工具&#xff0c;而是推荐使用bin目录下各种工具命令来管理Kafka&#xff0c; 这些工具命令其实用起…

vue3中标签form插件

想写一个系统&#xff0c;对八字进行标注&#xff0c;比如格局&#xff0c;有些八字就有很多格局&#xff0c;于是就想着使用el-tag但是&#xff0c;form表单中如何处理呢&#xff1f; 这个时候&#xff0c;就需要自己写一个,modelValue是表单的默认属性 <template><…

以 Serverfull 方式运行无服务器服务

当前 IT 架构中最流行的用例是从 Serverfull 转向 Serverless 设计。在某些情况下&#xff0c;我们可能需要以 Serverfull 方式设计服务或迁移到 Serverfull 作为运营成本的一部分。 在本文中&#xff0c;我们将展示如何将 Kumologica flow 作为 Docker 容器运行。通常&#x…

HarmonyOS页面和自定义组件生命周期

页面和自定义组件生命周期 在开始之前&#xff0c;我们先明确自定义组件和页面的关系&#xff1a; 自定义组件&#xff1a;Component装饰的UI单元&#xff0c;可以组合多个系统组件实现UI的复用。页面&#xff1a;即应用的UI页面。可以由一个或者多个自定义组件组成&#xff…

52、全连接 - 特征与样本空间的对应关系

上一节说到经过全连接层之后,神经网络学习到的特征,会从隐层特征空间逐步映射到样本空间,这主要是由于全连接层可以融合全局的特征。 在经过全连接层之后,在 ResNet50 这个神经网络中会输出1000个特征的得分值,这1000个特征的得分值,便可以对应到图像的分类。 怎么对应…

居然在Web上就可以体验下苹果电脑的操作系统啦?

发现一款宝藏项目 MacOS &#xff0c;在Web上打造一款原汁原味的 MacOS系统&#xff0c;不同于以外的仿操作系统的web应用&#xff0c;该应用底层基于 HTML5的 FileSystem 和 IndexedDB 构建了文件系统&#xff0c;理论上可以基于这套系统实现任何的上层应用。作者还制定了可以…

洛谷P1024[NOIP2001 提高组] 一元三次方程求解(cpp)(二分查找)

目录 1.题目 2.思路 3.AC 1.题目 # [NOIP2001 提高组] 一元三次方程求解 ## 题目描述 有形如&#xff1a; 这样的一个一元三次方程。给出该方程中各项的系数&#xff08;a,b,c,d 均为实数&#xff09;&#xff0c;并约定该方程存在三个不同实根&#xff08;根的范围在 -…