C# 观察者模式

news2024/11/19 9:26:43

一、概述

观察者模式是一种常用的设计模式,它属于行为型模式。在C#中,观察者模式通过定义一种一对多的依赖关系,使得当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这种模式可以实现松耦合,使得被观察者和观察者之间的关系更加灵活。

在C#中实现观察者模式通常需要以下几个角色:

1. Subject(主题):被观察者,它维护了一个观察者列表,并提供了添加、删除和通知观察者的方法。

2. Observer(观察者):观察者,它定义了一个更新方法,用于接收被观察者发出的通知。

3. ConcreteSubject(具体主题):具体的被观察者,它继承或实现了主题接口,并实现了具体的业务逻辑。它会在自身状态发生变化时通知观察者。

4. ConcreteObserver(具体观察者):具体的观察者,它继承或实现了观察者接口,并实现了更新方法。当接收到被观察者的通知时,它会执行相应的逻辑。

通过使用观察者模式,我们可以实现对象之间的解耦,使得它们之间的依赖关系更加灵活和可扩展。这种模式在事件处理、GUI开发以及许多其他场景中都有广泛应用。

观察者模式的优点和缺点:

观察者模式的优点:

1. 松耦合:被观察者和观察者之间的关系是松耦合的,它们可以独立变化而互不影响。

2. 可扩展性:可以方便地增加新的观察者,或者在不影响现有代码的情况下增加新的被观察者。

3. 易于维护:观察者模式将业务逻辑分散到各个观察者中,使得代码更加清晰、易于维护。

4. 支持广播通信:被观察者可以同时通知多个观察者,实现广播式的通信。

观察者模式的缺点:

1. 观察者过多时的性能问题:如果观察者过多或者观察者的更新操作比较耗时,可能会影响系统的性能。

2. 循环依赖问题:观察者和被观察者之间存在循环依赖的情况下,可能导致系统出现问题。

3. 更新顺序不确定:观察者模式中,观察者的更新顺序是不确定的,可能会导致一些意外的结果。

观察者模式适用于以下场景:

1. 当一个对象的状态变化需要通知其他多个对象,并且这些对象的行为需要根据该状态变化做出相应的调整时,可以使用观察者模式。

2. 当一个对象需要在不知道有多少个其他对象关注它的情况下,动态地将消息通知给这些对象时,可以使用观察者模式。

3. 当一个对象的改变需要同时影响其他多个对象,并且它不希望与这些对象形成紧耦合关系时,可以使用观察者模式。

4. 当系统中的某个对象需要与其他多个对象进行解耦,以降低对象之间的依赖性时,可以使用观察者模式。

5. 当需要实现事件驱动的系统或者消息通知机制时,可以使用观察者模式。

观察者模式适用于多个对象之间存在一对多的依赖关系,当一个对象的状态发生变化时,需要通知其他多个对象进行相应的处理。它能够实现对象之间的解耦,提高系统的灵活性和可扩展性。

二、代码实现

新建一个控制台项目

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 观察者模式
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ConcreteSubject subject = new ConcreteSubject();
            ConcreteObserver observer1 = new ConcreteObserver("Observer 1", subject);
            ConcreteObserver observer2 = new ConcreteObserver("Observer 2", subject);
            ConcreteObserver observer3 = new ConcreteObserver("Observer 3", subject);

            subject.AddObserver(observer1);
            subject.AddObserver(observer2);
            subject.AddObserver(observer3);

            subject.State = 1; // 触发通知
            subject.RemoveObserver(observer2);
            subject.State = 2; // 触发通知

            Console.ReadKey();
        }
    }

    // 主题接口
    public interface ISubject
    {
        void AddObserver(IObserver observer);
        void RemoveObserver(IObserver observer);
        void NotifyObservers();
    }

    // 具体主题
    public class ConcreteSubject : ISubject
    {
        private List<IObserver> observers = new List<IObserver>();
        private int state;

        public int State
        {
            get { return state; }
            set
            {
                state = value;
                NotifyObservers();
            }
        }

        public void AddObserver(IObserver observer)
        {
            observers.Add(observer);
        }

        public void RemoveObserver(IObserver observer)
        {
            observers.Remove(observer);
        }

        public void NotifyObservers()
        {
            foreach (IObserver observer in observers)
            {
                observer.Update();
            }
        }
    }

    // 观察者接口
    public interface IObserver
    {
        void Update();
    }

    // 具体观察者
    public class ConcreteObserver : IObserver
    {
        private string name;
        private ConcreteSubject subject;

        public ConcreteObserver(string name, ConcreteSubject subject)
        {
            this.name = name;
            this.subject = subject;
        }

        public void Update()
        {
            Console.WriteLine($"Observer {name} received an update. New state: {subject.State}");
        }
    }
}

运行:

三、解析代码

上面的代码看起来比较复杂,在23个设计模式中,其实还不算最复杂的,看多了习惯就好,下面大致的讲下代码逻辑。

首先是定义了一个接口 ISubject,它有三个方法,添加观察者,移除观察者,通告观察者。

然后 ConcreteSubject 继承了这个接口,这里重点在 state 这个属性这里:

public int State
{
    get { return state; }
    set
    {
        state = value;
        NotifyObservers();
    }
}

如果设置属性的值,就会调用 NotifyObservers 方法,这是个通知所有观察者的一个方法。

ConcreteObserver 存储了 name 和 具体观察者 ConcreteSubject 的实例(具体的主题),不过这里,只是用到了打印 subject.State ,并无其他作用。

Console.WriteLine($"Observer {name} received an update. New state: {subject.State}");

在 ConcreteSubject.AddObserver 方法的参数是一个 IObserver 接口,其实传递的就是 ConcreteObserver 实例,这里由于只需要调用 Update 方法,所以只用到了一个接口 IObserver,虽然保护了 ConcreteObserver 类的开放权限,但使的整个过程看起来更加复杂了。

从工作的角度来说,上面的很多写法并不是那么推荐,将简单的事情复杂化,是在自己在刁难自己,还可能会导致更多的 bug,可别忘了公司里还有项目经理,老板,他们可是一直在催你快点做,他们可不管你代码写的怎么样,他们根本就不懂代码。

在 Main 函数中,实例化了三个 ConcreteObserver 类,并传入了 name , 并添加到了 subject 中,这就是添加了三个观察者,以便后面用消息来通知他们

在设置 subject.State = 1 时,默认就调用了 NotifyObservers 方法,也就调用了三个观察者的 Update 方法,理解了,就会发现这些代码其实没有那么难。

四、案例

下面的代码来源博客 JiYF大男孩,写的很不错,拿来做个参考,链接在下面

https://www.cnblogs.com/JiYF/p/6896458.html

新建类 Blog

using System.Collections.Generic;

/// <summary>  
/// 订阅者接口  
/// </summary>  
public interface IObserver
{
    void Receive(Blog blog);
}

/// <summary>  
/// 订阅博客抽象类  
/// </summary>  
public abstract class Blog
{
    /// <summary>  
    /// 保存订阅者列表  
    /// </summary>  
    private List<IObserver> observers = new List<IObserver>();

    /// <summary>  
    /// 博主名  
    /// </summary>  
    public string BlogName { get; set; }

    /// <summary>  
    /// 博客标题  
    /// </summary>  
    public string BlogTitle { get; set; }

    /// <summary>  
    /// 博客信息  
    /// </summary>  
    public string BlogInfo { get; set; }

    /// <summary>  
    /// 博客构造函数  
    /// </summary>  
    /// <param name="blogTitle">博客标题</param>  
    /// <param name="blogInfo">博客信息</param>  
    public Blog(string name, string blogTitle, string blogInfo)
    {
        this.BlogName = name;
        this.BlogTitle = blogTitle;
        this.BlogInfo = blogInfo;
    }

    /// <summary>  
    /// 添加一个订阅者  
    /// </summary>  
    /// <param name="observer">具体的订阅者对象</param>  
    public void AddObserver(IObserver observer)
    {
        if (observers.Contains(observer))
        {
            return;
        }
        observers.Add(observer);
    }

    /// <summary>  
    /// 删除一个订阅者  
    /// </summary>  
    /// <param name="observer">具体的订阅者对象</param>  
    public void RemoveObserver(IObserver observer)
    {
        if (observers.Contains(observer))
        {
            observers.Remove(observer);
        }
    }

    /// <summary>  
    /// 发布博客通知  
    /// </summary>  
    public void PublishBlog()
    {
        //遍历通知每一个订阅者  
        foreach (IObserver ob in observers)
        {
            if (ob != null)
            {
                // 调用继承当前接口的Receive方法  
                ob.Receive(this);
            }
        }
    }
}  
 

新建类 JiYFBlog

namespace 设计模式_观察者模式
{
    /// <summary>  
    /// 具体的订阅博客类  
    /// </summary>  
    public class JiYFBlog : Blog
    {
        public JiYFBlog(string name, string blogTitile, string blogInfo)
            : base(name, blogTitile, blogInfo)
        {

        }
    }
}

新建类 Observer

using System;

namespace 设计模式_观察者模式
{
    /// <summary>  
    /// 具体的订阅者类  
    /// </summary>  
    public class Observer : IObserver
    {
        /// <summary>  
        /// 订阅者名字  
        /// </summary>  
        private string m_Name;
        public string Name
        {
            get { return m_Name; }
            set { m_Name = value; }
        }

        /// <summary>  
        /// 订阅者构造函数  
        /// </summary>  
        /// <param name="name">订阅者名字</param>  
        public Observer(string name)
        {
            this.m_Name = name;
        }

        /// <summary>  
        /// 订阅者接受函数  
        /// </summary>  
        /// <param name="blog"></param>  
        public void Receive(Blog blog)
        {
            Console.WriteLine("订阅者:\"{0}\"观察到了:{1}发布的一篇博客,标题为:{2},内容为:{3}", Name, blog.BlogName, blog.BlogTitle, blog.BlogInfo);
        }
    }
}

调用方法

using System;

namespace 设计模式_观察者模式
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("--全部订阅者--");
            // 创建一个 JiYF 的博客  
            // 多态的方式发布一条播客,但此时还没有订阅者  
            Blog jiyfBlog = new JiYFBlog("JiYF笨小孩", "丑小鸭", "丑小鸭的故事");

            // 创建订阅者  
            Observer obsZhangsan = new Observer("张三");
            Observer obsLiSi = new Observer("李四");
            Observer obsWangwu = new Observer("王五");

            // 添加到 JiYF 博客的订阅者  
            jiyfBlog.AddObserver(obsZhangsan);
            jiyfBlog.AddObserver(obsLiSi);
            jiyfBlog.AddObserver(obsWangwu);

            //通知订阅者  
            jiyfBlog.PublishBlog();

            Console.WriteLine();
            Console.WriteLine("--移除订阅者张三--");
            jiyfBlog.RemoveObserver(obsZhangsan);
            jiyfBlog.PublishBlog();


            Console.ReadLine();
        }
    }
}

运行:

 

end

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

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

相关文章

关键点检测中的对象关键点相似度

在不断发展的计算机视觉领域,理解物体的精确结构和姿态至关重要。无论是检测杂乱场景中的特定物体,还是实时分析人体姿势,关键点都起着至关重要的作用。对象上的这些独特点通常对应于角、边缘或其他可识别部分,用作识别和跟踪对象的锚点。但是我们如何衡量这些检测到的关键…

Java版本说明

java7 当谈到Java 7对应的JDK版本时&#xff0c;Java SE 7是Java 7的官方JDK版本。Java SE&#xff08;Standard Edition&#xff09;是Java平台的标准版本&#xff0c;它提供了Java编程语言的核心库和工具。 Java SE 7的JDK版本是JDK 7。你可以通过以下链接下载Java SE 7的J…

最新AI系统ChatGPT程序源码/支持GPT4/自定义训练知识库/GPT联网/支持ai绘画(Midjourney)+Dall-E2绘画/支持MJ以图生图

一、前言 SparkAi系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。 那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&#xff01…

Redis中常见的缓存穿透、缓存击穿、缓存雪崩、缓存预热解决方案

文章目录 一、缓存穿透1. 什么是缓存穿透2. 解决方案2.1 无效的key存放到Redis2.2 引入布隆过滤器2.3 如何选择&#xff1a; 二、缓存击穿1. 什么是缓存击穿2. 解决方案 三、缓存雪崩1. 什么是缓存雪崩2. 解决方案2.1 均匀过期2.2 热点数据缓存永远不过期2.3 采取限流降级的策略…

注册中心/配置管理 —— SpringCloud Consul

Consul 概述 Consul 是一个可以提供服务发现&#xff0c;健康检查&#xff0c;多数据中心&#xff0c;key/Value 存储的分布式服务框架&#xff0c;用于实现分布式系统的发现与配置。Cousul 使用 Go 语言实现&#xff0c;因此天然具有可移植性&#xff0c;安装包仅包含一个可执…

【C++学习手札】一文带你认识C++虚继承​​

食用指南&#xff1a;本文在有C基础的情况下食用更佳 &#x1f340;本文前置知识&#xff1a;C虚函数&#xff08;很重要&#xff0c;内部剖析&#xff09; ♈️今日夜电波&#xff1a;僕らのつづき—柊優花 1:06 ━━━━━━️&#x1f49f;──────── 3:51 …

将Nginx源码数组结构(ngx_array.c)和内存池代码单独编译运行,附代码

在上面一篇的基础上把Nginx源码数组结构也摘录下来&#xff0c;也增加了测试代码&#xff0c;编译运行。 https://blog.csdn.net/katerdaisy/article/details/132358883 《将nginx内存池代码单独编译运行&#xff0c;了解nginx内存池工作原理&#xff0c;附代码》 核心代码&…

C语言刷题训练DAY.8

1.计算单位阶跃函数 解题思路&#xff1a; 这个非常简单&#xff0c;只需要if else语句即可完成 解题代码&#xff1a; #include <stdio.h>int main() {int t 0;while(scanf("%d",&t)!EOF){if (t > 0)printf("1\n");else if (t < 0)pr…

LVS-DR集群(一台LVS,一台CIP,两台web,一台NFS)的构建以及LVS-DR模式工作原理和特点

一.LVS-DR工作模式原理和特点 1.工作模式 2.模式特点 二.构建环境 1.五台关闭防火墙&#xff0c;关闭selinux&#xff0c;拥有固定IP&#xff0c;部署有http服务的虚拟机&#xff0c;LVS设备下载ipvsadm工具&#xff0c;NFS 设备需要下载rpcbind和nfs-utils 2.实现功能 3…

win11调整屏幕亮度

1.右键打开 2.显示更多选项 3.NVIDIA控制面板 4.调整桌面颜色设置 5.亮度

linux 搭建 nexus maven私服

环境&#xff1a; 必须在 linux 环境下&#xff0c;并且已安装 jdk 下载 访问百度网盘链接: https://pan.baidu.com/s/1fHGmQ2jRUAsXyPom2KL8Mw?pwd0000 提取码: 0000 官网下载 Download Archives - Repository Manager 3 (sonatype.com) 部署 &#xff1a; 进入目录&#…

Echarts:象形柱图实现水塔水位的动画、水球图和液位柱子图

一、象形柱图 1、vue中使用象形柱图 效果图&#xff1a; 2、代码实现 <template><div :class"className" :style"{height:height,width:width}"/></template> <script>import echarts from echarts require(echarts/theme/macar…

论文浅尝 | KRACL-利用图上下文和对比学习的稀疏KG补全

笔记整理&#xff1a;李娟&#xff0c;浙江大学博士&#xff0c;研究方向为知识图谱表示学习 论文链接&#xff1a;https://arxiv.org/pdf/2208.07622.pdf 代码链接&#xff1a;https://github.com/TamSiuhin/KRACL 介绍 知识图谱&#xff08;KG&#xff09;通常是不完整的&…

注意力机制——SENet原理详解及源码解析

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;专栏推荐&#xff1a;深度学习网络原理与实战 &#x1f34a;近期目标&#xff1a;写好专栏的每一篇文章 &#x1f34a;支持小苏&#xff1a;点赞&#x1f44d;&#x1f3fc;、…

【ROS】话题通信--从理论介绍到模型实现(C++)

1.简单介绍 话题通信是ROS中使用频率最高的一种通信模式&#xff0c;话题通信是基于发布订阅模式的&#xff0c;也即:一个节点发布消息&#xff0c;另一个节点订阅该消息。像雷达、摄像头、GPS… 等等一些传感器数据的采集&#xff0c;也都是使用了话题通信&#xff0c;换言之…

windows 配置 Kerberos客户端访问CDH组件

0.背景 想在window机器上访问内网集群的CDH组件(如solr的webui),由于集群配置了Kerberos验证,所以需要配置相关,否则打开webui会有401未授权错误 1. 流程 1.1 windows安装Kerberos客户端 -下载 Windows系统客户端去下面网站按需下载 http://web.mit.edu/kerberos/dist/ 需要…

【力扣】496. 下一个更大元素 I <单调栈、模拟>

【力扣】496. 下一个更大元素 I nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。给你两个没有重复元素的数组 nums1 和 nums2 &#xff0c;下标从 0 开始计数&#xff0c;其中nums1 是 nums2 的子集。   对于每个 0 < i <…

喜报 | 擎创再度入围IDC中国FinTech 50榜单

8月16日&#xff0c;2023年度“IDC中国FinTech 50”榜单正式揭晓&#xff0c;擎创科技继2022年入选该榜单后&#xff0c;再次以创新者姿态成功入选&#xff0c;并以技术赋能业务创新&#xff0c;成为中国金融科技领域创新与活力的重要贡献者。 “IDC中国FinTech 50”旨在评选出…

RabbitMq交换机类型介绍

RabbitMq交换机类型介绍 在RabbitMq中&#xff0c;生产者的消息都是通过交换器来接收&#xff0c;然后再从交换器分发到不同的队列&#xff0c;再由消费者从队列获取消息。这种模式也被成为“发布/订阅”。 分发的过程中交换器类型会影响分发的逻辑。 直连交换机&#xff1a…

高校大学生社团管理系统的设计与实现(论文+源码)_kaic

目 录 一、绪论 &#xff08;一&#xff09;选题背景 1、社团管理系统的提出 &#xff08;二&#xff09;系统设计的原则与目标 1、系统设计原则 2、系统设计目标 二、系统关键技术的分析 &#xff08;一&#xff09;JSP技术 &#xff08;二&#xff09;Tomcat简介 1、SERVL…