设计模式| 观察者模式 Observer Pattern详解

目录

  • 一、概述

    • [1.1 动机](#1.1 动机)
    • [1.2 核心思想](#1.2 核心思想)
    • [1.3 别名](#1.3 别名)
  • 二、角色与实现原理

    • [2.1 角色](#2.1 角色)
    • [2.2 实现原理](#2.2 实现原理)
    • [2.3 类图](#2.3 类图)
  • 三、经典接口实现

    • [3.1 示例](#3.1 示例)
      • [3.1.1 观察者接口](#3.1.1 观察者接口)
      • [3.1.2 目标接口](#3.1.2 目标接口)
      • [3.1.3 具体被观察者](#3.1.3 具体被观察者)
      • [3.1.4 具体观察者](#3.1.4 具体观察者)
      • [3.1.5 Client](#3.1.5 Client)
      • [3.1.6 UML时序图](#3.1.6 UML时序图)
    • [3.2 特点](#3.2 特点)
  • 四、其他实现方式

    • [4.1 委托与事件(.NET 原生实现)](#4.1 委托与事件(.NET 原生实现))
      • [4.1.1 示例](#4.1.1 示例)
      • [4.1.2 UML类图](#4.1.2 UML类图)
      • [4.1.3 特点](#4.1.3 特点)
      • [4.1.4 适用场景](#4.1.4 适用场景)
    • [4.2 IObservable<T> 和 IObserver<T> 接口](#4.2 IObservable 和 IObserver 接口)
      • [4.2.1 接口概述](#4.2.1 接口概述)
        • [4.2.1.1 被观察者接口 : IObservable<out T>](#4.2.1.1 被观察者接口 : IObservable)
        • [4.2.1.2 观察者接口 : IObserver<in T>](#4.2.1.2 观察者接口 : IObserver)
      • [4.2.2 示例](#4.2.2 示例)
        • [4.2.2.1 具体被观察者Subject:实现 IObservable<T>](#4.2.2.1 具体被观察者Subject:实现 IObservable)
          • [4.2.2.1.1 订阅管理 (`Subscribe` 方法)](#4.2.2.1.1 订阅管理 (Subscribe 方法))
          • [4.2.2.1.2 取消订阅 (`Unsubscriber`类)](#4.2.2.1.2 取消订阅 (Unsubscriber类))
          • [4.2.2.1.3 状态通知 (`NotifyObservers` 方法)](#4.2.2.1.3 状态通知 (NotifyObservers 方法))
          • [4.2.2.1.4 完成与错误通知 (`OnCompleted` 和 `OnError`)](#4.2.2.1.4 完成与错误通知 (OnCompletedOnError))
          • [4.2.2.1.5 线程安全设计](#4.2.2.1.5 线程安全设计)
          • [4.2.2.1.6 全部代码](#4.2.2.1.6 全部代码)
      • [4.2.3 UML类图](#4.2.3 UML类图)
      • [4.2.4 扩展内容](#4.2.4 扩展内容)
        • [4.2.4.1 异步通知](#4.2.4.1 异步通知)
        • [4.2.4.2 事件过滤](#4.2.4.2 事件过滤)
      • [4.2.5 特点](#4.2.5 特点)
      • [4.2.6 适用场景](#4.2.6 适用场景)
    • [4.3 System.Reactive](#4.3 System.Reactive)
      • [4.3.1 安装](#4.3.1 安装)
      • [4.3.2 示例](#4.3.2 示例)
      • [4.3.3 特点](#4.3.3 特点)
      • [4.3.4 适用场景](#4.3.4 适用场景)
  • 五、使用场景

  • 六、扩展

    • [6.1 初始化和基础架构搭建](#6.1 初始化和基础架构搭建)
      • [6.1.1 初始化和基础架构搭建](#6.1.1 初始化和基础架构搭建)
        • [6.1.1.1 一个观察者观察多个目标](#6.1.1.1 一个观察者观察多个目标)
        • [6.1.1.2 **目标多而观察者少**](#6.1.1.2 目标多而观察者少)
        • [6.1.1.3 **目标对象之间存在复杂依赖关系**](#6.1.1.3 目标对象之间存在复杂依赖关系)
    • [6.2 注册机制](#6.2 注册机制)
      • [6.2.1 问题](#6.2.1 问题)
      • [6.2.2 解决方案](#6.2.2 解决方案)
        • [6.2.2.1 实现思路](#6.2.2.1 实现思路)
        • [6.2.2.2 示例](#6.2.2.2 示例)
        • [6.2.2.3 优点](#6.2.2.3 优点)
        • [6.2.2.4 适用场景](#6.2.2.4 适用场景)
    • [6.3 触发机制](#6.3 触发机制)
      • [6.3.1 触发之前](#6.3.1 触发之前)
      • [6.3.2 更新的触发者](#6.3.2 更新的触发者)
      • [6.3.3 示例](#6.3.3 示例)
        • [6.3.3.1 抽象目标对象](#6.3.3.1 抽象目标对象)
        • [6.3.3.2 观察者接口](#6.3.3.2 观察者接口)
        • [6.3.3.3 具体的的观察者](#6.3.3.3 具体的的观察者)
        • [6.3.3.4 自动触发的目标对象](#6.3.3.4 自动触发的目标对象)
        • [6.3.3.5 手动触发的目标对象](#6.3.3.5 手动触发的目标对象)
        • [6.3.3.6 使用示例](#6.3.3.6 使用示例)
    • [6.4 **信息传递机制**](#6.4 信息传递机制)
      • [6.4.1 解决方案与实现方式](#6.4.1 解决方案与实现方式)
        • [6.4.1.1 推模型(Push Model)](#6.4.1.1 推模型(Push Model))
        • [6.4.1.2 拉模型(Pull Model)](#6.4.1.2 拉模型(Pull Model))
        • [6.4.1.3 设计对比与权衡](#6.4.1.3 设计对比与权衡)
        • [6.4.1.4 最佳实践建议](#6.4.1.4 最佳实践建议)
    • [6.5 资源管理和错误处理](#6.5 资源管理和错误处理)
  • 参考

    设计模式:可复用面向对象软件的基础(典藏版) - 埃里克·伽玛 - 微信读书

    第七章:C#响应式编程System.Reactive - 平元兄 - 博客园

    ReactiveX · ReactiveX文档中文翻译

  • 项目地址

    NitasDemo/10DesignPatterns/DesignPatterns/ObserverPattern at main · Nita121388/NitasDemo

一、概述

观察者模式是一种行为设计模式,用于定义对象之间的一对多依赖关系。当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会自动得到通知并更新。

1.1 动机

将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了其可复用性。

1.2 核心思想

  • 解耦:将被观察者与观察者解耦,使它们之间通过接口或事件机制交互。
  • 自动通知:当被观察者状态改变时,自动通知所有观察者。

1.3 别名

  1. 依赖模式:强调对象间的依赖关系。

  2. 发布--订阅模式:发布者将消息发送给多个订阅者。

  3. 模型-视图模式:模型变化时,视图自动更新。

  4. 源-监听器模式:事件源触发事件后通知所有监听器。

  5. 从属者模式:从属者依赖于其他对象的状态变化。

    这些别名都体现了观察者模式的核心思想:定义对象间的一对多依赖关系,实现状态变化的自动通知。

二、角色与实现原理

2.1 角色

  1. Subject(目标/主题/被观察者)
    • 维护一个观察者列表,允许观察者订阅或取消订阅。
    • 当状态改变时,通知所有观察者。
  2. Observer(观察者)
    • 观察者将对观察目标的改变做出反应。观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
  3. ConcreteSubject(具体被观察者)
    • 实现Subject接口,维护自身状态,并在状态改变时通知观察者。
  4. ConcreteObserver(具体观察者)
    • 实现Observer接口,根据被观察者的状态改变做出相应反应。

2.2 实现原理

  1. 被观察者维护观察者列表

    • 被观察者类中包含一个观察者列表,用于存储所有订阅的观察者对象。
    1. 被观察者类可以注册与注销观察者,提供方法允许观察者对象注册到被观察者列表中,或从列表中注销。
  2. 通知机制

    • 当被观察者状态改变时,遍历观察者列表,调用每个观察者的更新方法。

2.3 类图

implements implements notify 1..* Subject +List observers +Attach(observer: observer) : void +Detach(observer: observer) : void +notify() : void <<Interface>> Observer +update() : void ConcreteSubject +state: String +setState(state: String) : void +getState() : String ConcreteObserver +update() : void

三、经典接口实现

Gang of Four(GoF)是指四位著名软件设计模式专家(Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides)在1994年出版的《设计模式》

GoF模式本质 *:通过接口规范化观察者模式中的角色职责,强调设计契约优先,适用于需要长期维护、高可扩展性的复杂系统架构设计。*

核心思想:通过显式接口定义观察者和主题的关系。

3.1 示例

3.1.1 观察者接口

c# 复制代码
// 观察者接口
public interface IObserver
{
    void Update(string message);
}

3.1.2 目标接口

c# 复制代码
// 目标接口
public interface ISubject
{
    void Attach(IObserver observer);
    void Detach(IObserver observer);
    void Notify();
}

3.1.3 具体被观察者

c# 复制代码
// 具体目标
public class ConcreteSubject : ISubject
{
    // 观察者列表
    private List<IObserver> _observers = new();
    // 目标状态
    private string _state;

    // 注册观察者: 将观察者对象注册到目标对象中
    public void Attach(IObserver observer) => _observers.Add(observer);
    // 注销观察者: 移除一个观察者
    public void Detach(IObserver observer) => _observers.Remove(observer);
    
    // 通知观察者: 改变目标对象的状态,触发通知
    public void Notify()
    {
        foreach (var observer in _observers)
            observer.Update(_state);
    }

    // 设置目标状态: 改变目标对象的状态
    public void SetState(string state)
    {
        _state = state;
        Notify();
    }
}

3.1.4 具体观察者

c# 复制代码
// 具体观察者
public class ConcreteObserver : IObserver
{
    // 接收通知并处理
    public void Update(string message) => Console.WriteLine($"Received: {message}");
}

3.1.5 Client

c# 复制代码
using System;

class Program
{
    static void Main(string[] args)
    {
        // 创建具体目标对象
        ConcreteSubject subject = new ConcreteSubject();

        // 创建多个具体观察者对象
        ConcreteObserver observer1 = new ConcreteObserver();
        ConcreteObserver observer2 = new ConcreteObserver();
        ConcreteObserver observer3 = new ConcreteObserver();

        // 将观察者对象注册到目标对象中
        subject.Attach(observer1);
        subject.Attach(observer2);
        subject.Attach(observer3);

        // 改变目标对象的状态,触发通知
        Console.WriteLine("第一次状态更新:");
        subject.SetState("Hello, Observers!");

        // 移除一个观察者
        subject.Detach(observer2);

        // 再次改变目标对象的状态,触发通知
        Console.WriteLine("\n第二次状态更新:");
        subject.SetState("State has changed!");
    }
}

结果:

c# 复制代码
第一次状态更新:
Received: Hello, Observers!
Received: Hello, Observers!
Received: Hello, Observers!

第二次状态更新:
Received: State has changed!
Received: State has changed!

3.1.6 UML时序图

Client ConcreteSubject ConcreteObserver1 ConcreteObserver2 ConcreteObserver3 创建() 创建() 创建() 创建() Attach(Observer1) Attach(Observer2) Attach(Observer3) 第一次状态更新 SetState("Hello, Observers!") Notify() Update("Hello...") Update("Hello...") Update("Hello...") loop 遍历观察者 Detach(Observer2) 第二次状态更新 SetState("State has changed!") Notify() Update("State...") Update("State...") loop 遍历剩余观察者 Client ConcreteSubject ConcreteObserver1 ConcreteObserver2 ConcreteObserver3

3.2 特点

  • 符合设计模式原生定义,代码结构清晰。
  • 强类型约束,编译时检查接口实现。
  • 对语言无特殊要求,通用性强。
  • 显式依赖关系,逻辑透明。
  • 适用于简单场景,无需框架支持。

四、其他实现方式

4.1 委托与事件(.NET 原生实现)

  • 机制:利用语言或框架提供的事件监听机制,被观察者触发事件,观察者通过监听器接收事件。

4.1.1 示例

c# 复制代码
public class EventSubject
{
    public event EventHandler<string> StateChanged;

    private string _state;
    public void SetState(string state)
    {
        _state = state;
        StateChanged?.Invoke(this, _state);
    }
}

public class EventObserver
{
    public void Subscribe(EventSubject subject)
    {
        subject.StateChanged += HandleStateChange;
    }

    private void HandleStateChange(object sender, string message)
    {
        Console.WriteLine($"Event received: {message}");
    }

    public void Unsubscribe(EventSubject subject)
    {
        subject.StateChanged -= HandleStateChange;
    }
}
class Program
    {
        static void Main(string[] args)
        {
            // 创建 EventSubject 和 EventObserver 对象
            EventSubject subject = new EventSubject();
            EventObserver observer = new EventObserver();

            // 订阅事件
            observer.Subscribe(subject);
            Console.WriteLine("Observer has subscribed to the subject.");

            // 改变状态,触发事件
            subject.SetState("State 1");
            subject.SetState("State 2");

            // 取消订阅
            observer.Unsubscribe(subject);
            Console.WriteLine("Observer has unsubscribed from the subject.");

            // 再次改变状态,观察是否还会触发事件
            subject.SetState("State 3"); // 不会触发事件,因为已取消订阅
        }
    }

结果

text 复制代码
Observer has subscribed to the subject.
Event received: State 1
Event received: State 2
Observer has unsubscribed from the subject.

4.1.2 UML类图

uses EventSubject -string _state +event EventHandler StateChanged +void SetState(string state) EventObserver +void Subscribe(EventSubject subject) -void HandleStateChange(object sender, string message) +void Unsubscribe(EventSubject subject)

4.1.3 特点

  • 代码更加简洁,轻量级,利用语言的内置特性,减少了手动管理观察者列表的复杂性。
  • 内置线程安全的事件触发机制(?.Invoke)
  • 支持多播(多个观察者订阅同一事件)
    • 对于一些复杂的业务逻辑,可能无法完全满足需求,因为事件机制通常是基于固定的事件类型和参数进行设计的,不够灵活。
    • 而且如果事件的定义不合理,可能会导致系统的可扩展性和维护性变差。
  • 无法跨模块解耦(需直接访问事件)。

4.1.4 适用场景

  • GUI 事件处理(如按钮点击)。
  • 单模块内的局部解耦。
  • 适合简单通知逻辑且不涉及复杂数据流的场景。

4.2 IObservable 和 IObserver 接口

核心思想:使用.NET框架内置的观察者模式标准化接口。

4.2.1 接口概述

接口 角色 职责
`IObservable<T>` 被观察对象 数据/事件的生产者
`IObserver<T>` 观察者 数据/事件的消费者
4.2.1.1 被观察者接口 : IObservable
c# 复制代码
namespace System
{
    /// <summary>
    /// 定义了一个基于推送的事件通知提供者/ 被观察者
    /// </summary>
    /// <typeparam name="T">提供通知信息的对象类型。</typeparam>
    public interface IObservable<out T>
    {
        /// <summary>
        /// 通知提供者有一个观察者将要接收通知。
        /// </summary>
        /// observer">将要接收通知的对象。
        /// <returns>一个接口引用,允许观察者在提供者完成发送通知之前停止接收通知。</returns>
        IDisposable Subscribe(IObserver<T> observer);
    }
}

IObservable<T>是一个接口,属于 C# 中的事件驱动编程模型,是响应式编程(Reactive Programming)的核心接口之一。

它定义了一个基于推送的事件通知机制,允许观察者(IObserver<T>订阅 通知源(IObservable<T>),并在通知源产生数据或事件时接收通知。

  • 泛型参数T:表示通知中携带的数据类型。
  • Subscribe方法:是IObservable<T>的核心方法。
    1. 它接收一个实现了IObserver<T>接口的对象作为参数,表示观察者。
    2. 当调用Subscribe方法时,观察者会注册到通知源,从而能够接收通知。
    3. 方法返回一个IDisposable对象,观察者可以通过调用其Dispose方法来取消订阅,停止接收通知。
4.2.1.2 观察者接口 : IObserver
c# 复制代码
namespace System
{
    /// <summary>
    /// 提供一种接收基于推送的通知的机制。//观察者
    /// </summary>
    /// <typeparam name="T">提供通知信息的对象类型。</typeparam>
    public interface IObserver<in T>
    {
        /// <summary>
        /// 向观察者提供新的数据。
        /// </summary>
        /// <param name="value">当前的通知信息。</param>
        void OnNext(T value);

        /// <summary>
        /// 通知观察者提供者遇到了错误条件。
        /// </summary>
        /// <param name="error">一个提供有关错误的额外信息的对象。</param>
        void OnError(Exception error);

        /// <summary>
        /// 通知观察者提供者已经完成发送基于推送的通知。
        /// </summary>
        void OnCompleted();
    }
}

IObserver<T>它定义了一个观察者的角色,用于接收来自通知源(IObservable<T>)的推送通知。

  • 泛型参数T:表示通知中携带的数据类型。
  • OnNext方法:当通知源有新的数据可用时,调用此方法向观察者传递数据。参数value是当前的通知信息。
  • OnError方法:当通知源在发送通知过程中遇到错误时,调用此方法通知观察者。参数error是一个Exception对象,提供有关错误的详细信息。
  • OnCompleted方法:当通知源完成所有通知的发送后,调用此方法通知观察者。这表示通知源不会再发送任何新的通知。

4.2.2 示例

4.2.2.1 具体被观察者Subject:实现 IObservable
4.2.2.1.1 订阅管理 (Subscribe 方法)
c# 复制代码
public IDisposable Subscribe(IObserver<string> observer) {
    lock (_lock) {
        if (!_observers.Contains(observer)) {
            _observers.Add(observer);
        }
    }
    return new Unsubscriber(_observers, observer, _lock);
}
4.2.2.1.2 取消订阅 (Unsubscriber类)
c# 复制代码
private class Unsubscriber : IDisposable {
    // ... 略去字段和构造函数 ...
    public void Dispose() {
        lock (_lock) {
            if (_observer != null && _observers.Contains(_observer)) {
                _observers.Remove(_observer);
                _observer = null;
            }
        }
    }
}
  • 功能 :调用 Dispose() 时从观察者列表中移除目标观察者。
  • 资源释放:移除后置空引用,避免内存泄漏。
  • 线程安全 :通过 lock 确保取消订阅的原子性。
4.2.2.1.3 状态通知 (NotifyObservers 方法)
c# 复制代码
public void NotifyObservers(string state) {
    lock (_lock) {
        foreach (var observer in _observers) {
            observer.OnNext(state);
        }
    }
}
  • 功能 :遍历所有观察者,调用其 OnNext 方法推送新状态。
  • 线程安全:遍历期间锁定列表,防止并发修改。
4.2.2.1.4 完成与错误通知 (OnCompletedOnError)
c# 复制代码
public void OnCompleted() {
    lock (_lock) {
        foreach (var observer in _observers) {
            observer.OnCompleted();
        }
        _observers.Clear();
    }
}

public void OnError(Exception error) {
    lock (_lock) {
        foreach (var observer in _observers) {
            observer.OnError(error);
        }
        _observers.Clear();
    }
}
  • 完成通知 :调用所有观察者的 OnCompleted(),清空列表(终止后续通知)。
  • 错误通知 :调用所有观察者的 OnError(),清空列表。
  • 线程安全:全程加锁。
4.2.2.1.5 线程安全设计
  • 锁对象_lock :所有对观察者列表的操作(增、删、遍历)均通过 lock (_lock) 确保原子性。
  • 场景覆盖
    • 多线程同时订阅/取消订阅。
    • 通知过程中触发新的订阅/取消订阅。
4.2.2.1.6 全部代码
  • 具体目标

    c# 复制代码
    using System;
    using System.Collections.Generic;
    using System.Threading;
    
    // Subject 类实现了 IObservable<string> 接口,用于管理观察者并通知状态变化
    public class Subject : IObservable<string>
    {
        // 用于存储所有订阅的观察者
        private List> _observers = new();
        // 用于线程安全的锁对象
        private readonly object _lock = new();
    
        // 订阅方法,允许观察者订阅状态变化
        public IDisposable Subscribe(IObserver observer)
        {
            lock (_lock) // 确保线程安全
            {
                if (!_observers.Contains(observer)) // 防止重复订阅
                {
                    _observers.Add(observer);
                }
            }
            // 返回一个 Unsubscriber 对象,用于取消订阅
            return new Unsubscriber(_observers, observer, _lock);
        }
    
        // Unsubscriber 类实现了 IDisposable 接口,用于取消观察者的订阅
        private class Unsubscriber : IDisposable
        {
            private List> _observers;
            private IObserver _observer;
            private readonly object _lock;
    
            // 构造函数,初始化观察者列表、当前观察者和锁对象
            public Unsubscriber(List> observers, IObserver observer, object lockObj)
            {
                _observers = observers;
                _observer = observer;
                _lock = lockObj;
            }
    
            // Dispose 方法用于取消订阅
            public void Dispose()
            {
                lock (_lock) // 确保线程安全
                {
                    if (_observer != null && _observers.Contains(_observer))
                    {
                        _observers.Remove(_observer); // 从观察者列表中移除当前观察者
                        _observer = null; // 清空当前观察者引用
                    }
                }
            }
        }
    
        // SetState 方法用于设置状态并通知所有观察者
        public void NotifyObservers(string state)
        {
            lock (_lock) // 确保线程安全
            {
                foreach (var observer in _observers)
                {
                    observer.OnNext(state); // 调用观察者的 OnNext 方法通知状态变化
                }
            }
        }
    
        // OnCompleted 方法用于通知所有观察者完成事件
        public void OnCompleted()
        {
            lock (_lock) // 确保线程安全
            {
                foreach (var observer in _observers)
                {
                    observer.OnCompleted(); // 调用观察者的 OnCompleted 方法通知完成事件
                }
                _observers.Clear(); // 清空观察者列表
            }
        }
    
        // OnError 方法用于通知所有观察者发生错误
        public void OnError(Exception error)
        {
            lock (_lock) // 确保线程安全
            {
                foreach (var observer in _observers)
                {
                    observer.OnError(error); // 调用观察者的 OnError 方法通知错误事件
                }
                _observers.Clear(); // 清空观察者列表
            }
        }
    }
  • 具体观察者

    ConcreteObserver类实现了IObserver<string>接口,用于接收被观察者的状态变化通知。

    • OnNext方法:接收状态变化通知,并输出状态信息。
    • OnError方法:接收错误通知,并输出错误信息。
    • OnCompleted方法:接收完成通知,并输出完成信息。
    c# 复制代码
    // ConcreteObserver 类实现了 IObserver<string> 接口,用于接收状态变化通知
    public class ConcreteObserver : IObserver<string>
    {
        // 观察者的名称,用于区分不同的观察者
        private readonly string _name;
    
        // 构造函数,初始化观察者名称
        public ConcreteObserver(string name)
        {
            _name = name;
        }
    
        // OnNext 方法用于接收状态变化通知
        public void OnNext(string value)
        {
            Console.WriteLine($"{_name} received: {value}"); // 输出接收到的状态信息
        }
    
        // OnError 方法用于接收错误通知
        public void OnError(Exception error)
        {
            Console.WriteLine($"{_name} received an error: {error.Message}"); // 输出错误信息
        }
    
        // OnCompleted 方法用于接收完成通知
        public void OnCompleted()
        {
            Console.WriteLine($"{_name} received completion notification."); // 输出完成通知
        }
    }
  • 客户端使用实例(Client)

    c# 复制代码
    using System;
    
    namespace IObservableTDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                // 1. 创建被观察者和观察者
                Subject subject = new Subject();
                ConcreteObserver observer1 = new ConcreteObserver("observer 1");
                ConcreteObserver observer2 = new ConcreteObserver("observer 2");
    
                // 2. 订阅观察者
                IDisposable subscription1 = subject.Subscribe(observer1);
                IDisposable subscription2 = subject.Subscribe(observer2);
    
                // 状态通知
                subject.NotifyObservers("Hello, World!");
                
                // 取消订阅 observer2
                subscription2.Dispose();
                
                // 再次设置状态,观察者1会收到通知,观察者2不会收到
                subject.NotifyObservers("Hello again!");
                
                // 模拟错误 此时会清空观察者列表
                subject.OnError(new Exception("Something went wrong!"));
                
                // 再次设置状态,观察者1和观察者2都不会收到通知
                subject.NotifyObservers("Hello again!");
    
                // 再次订阅观察者
                IDisposable subscription3 = subject.Subscribe(observer1);
                
                // 再次设置状态,观察者1收到通知
                subject.NotifyObservers("Hello again!");
                
                // 完成通知
                subject.OnCompleted();
                
                //再次设置状态,都不会收到通知
                subject.NotifyObservers("Hello again!");
    
                // 等待用户输入后退出
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }
    }
  • 结果

    text 复制代码
    Observer 1 received: Hello, World!
    Observer 2 received: Hello, World!
    Observer 1 received: Hello again!
    Observer 1 received an error: Something went wrong!
    Observer 3 received: Hello again!
    Observer 3 received completion notification.
    Press any key to exit...

4.2.3 UML类图

Implements Implements Implements Creates Notifies <<interface>> IObservable<out T> +Subscribe(IObserver<T> observer) : IDisposable <<interface>> IObserver<in T> +OnNext(T value) : void +OnError(Exception error) : void +OnComplete() : void <<interface>> IDisposable +Dispose() : void Subject -List<IObserver>string<> _observers -object _lock +Subscribe(IObserver<string> observer) : IDisposable +NotifyObservers(string state) : void +OnCompleted() : void +OnError(Exception error) : void Unsubscriber -List<IObserver>string<> _observers -IObserver<string> _observer -object _lock +Unsubscriber(List<IObserver>string<> observers, IObserver<string> observer, object lockObj) +Dispose() : void ConcreteObserver -string _name +OnNext(string value) : void +OnError(Exception error) : void +OnComplete() : void

4.2.4 扩展内容

4.2.4.1 异步通知

可以通过Taskasync/await来实现异步通知。

  • 异步接口

    • 每个方法都返回Task以支持异步操作
    • 完全异步的观察者接口
    c# 复制代码
    public interface IAsyncObserver<in T>
    {
        Task OnNextAsync(T value);
        Task OnErrorAsync(Exception exception);
        Task OnCompletedAsync();
    } 
  • 顺序异步通知机制

    • 严格按顺序通知观察者
    • 每个观察者处理完成后再通知下一个
    • 保留通知顺序性
    c# 复制代码
    foreach (var observer in observersCopy)
    {
        try
        {
            await observer.OnNextAsync(value);
        }
        // ...
    }
  • 完整代码

    c# 复制代码
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using System.Threading;
    
    #region Client Code
    var subject = new AsyncSubject<string>();
            
    var observer1 = new AsyncObserver("observer 1");
    var observer2 = new AsyncObserver("observer 2");
    
    // 订阅观察者
    using var subscription1 = subject.Subscribe(observer1);
    using var subscription2 = subject.Subscribe(observer2);
    
    // 异步通知
    await subject.NotifyAsync("First Message");
            
    // 取消订阅 observer2
    subscription2.Dispose();
    
    // 再次通知
    await subject.NotifyAsync("Second Message");
            
    // 错误通知
    await subject.NotifyErrorAsync(new Exception("Test Error"));
            
    // 完成通知
    await subject.OnCompletedAsync();
    
    Console.WriteLine("Press any key to exit...");
    Console.ReadKey();
    #endregion
    
    #region Interfaces
    public interface IAsyncObserver<in T>
    {
        Task OnNextAsync(T value);
        Task OnErrorAsync(Exception exception);
        Task OnCompletedAsync();
    }
    
    public interface IAsyncObservable<out T>
    {
        IDisposable Subscribe(IAsyncObserver observer);
    }
    #endregion
    
    #region Async Subject
    public class AsyncSubject<T> : IAsyncObservable<T>
    {
        private readonly List> _observers = new();
        private readonly object _lock = new();
    
        public IDisposable Subscribe(IAsyncObserver observer)
        {
            lock (_lock)
            {
                if (!_observers.Contains(observer))
                {
                    _observers.Add(observer);
                }
            }
            return new Unsubscriber(() =>
            {
                lock (_lock)
                {
                    _observers.Remove(observer);
                }
            });
        }
    
        public async Task NotifyAsync(T value)
        {
            IAsyncObserver<T>[] observersCopy;
            lock (_lock)
            {
                observersCopy = _observers.ToArray();
            }
    
            foreach (var observer in observersCopy)
            {
                try
                {
                    await observer.OnNextAsync(value);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Notification failed: {ex.Message}");
                }
            }
        }
    
        public async Task NotifyErrorAsync(Exception error)
        {
            IAsyncObserver<T>[] observersCopy;
            lock (_lock)
            {
                observersCopy = _observers.ToArray();
                _observers.Clear();
            }
    
            foreach (var observer in observersCopy)
            {
                try
                {
                    await observer.OnErrorAsync(error);
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Error notification failed: {ex.Message}");
                }
            }
        }
    
        public async Task OnCompletedAsync()
        {
            IAsyncObserver<T>[] observersCopy;
            lock (_lock)
            {
                observersCopy = _observers.ToArray();
                _observers.Clear();
            }
    
            foreach (var observer in observersCopy)
            {
                try
                {
                    await observer.OnCompletedAsync();
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Completion notification failed: {ex.Message}");
                }
            }
        }
    
        private class Unsubscriber : IDisposable
        {
            private readonly Action _unsubscribeAction;
    
            public Unsubscriber(Action unsubscribeAction)
            {
                _unsubscribeAction = unsubscribeAction;
            }
    
            public void Dispose() => _unsubscribeAction?.Invoke();
        }
    }
    #endregion
    
    #region Async Observer
    public class AsyncObserver : IAsyncObserver<string>
    {
        private readonly string _name;
    
        public AsyncObserver(string name) => _name = name;
    
        public async Task OnNextAsync(string value)
        {
            await Task.Delay(100); // 模拟异步处理
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] {_name} received: {value}");
        }
    
        public async Task OnErrorAsync(Exception exception)
        {
            await Task.Delay(100); // 模拟异步处理
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] {_name} error: {exception.Message}");
        }
    
        public async Task OnCompletedAsync()
        {
            await Task.Delay(100); // 模拟异步处理
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] {_name} completed");
        }
    }
    #endregion

    结果

    c# 复制代码
    [22:14:48.269] Observer 1 received: First Message
    [22:14:48.449] Observer 2 received: First Message
    [22:14:48.554] Observer 1 received: Second Message
    [22:14:48.662] Observer 1 error: Test Error
    Press any key to exit...
4.2.4.2 事件过滤

可以通过在通知方法中添加过滤逻辑来实现事件过滤。

  • FilteredObservable的构造函数接收一个过滤函数(Func<T, bool>),用于决定哪些消息需要通知给观察者。
  • Notify方法中,只有满足过滤条件的消息才会被发送。
c# 复制代码
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

#region  Client Code

var subject = new FilteredObservable<string>(s => s.StartsWith("[IMPORTANT]"));
        
var observer1 = new Observer("Observer 1");
var observer2 = new Observer("Observer 2");

using var subscription1 = subject.Subscribe(observer1);
using var subscription2 = subject.Subscribe(observer2);

// 这些消息将被过滤
subject.Notify("Normal Message 1");
subject.Notify("Normal Message 2");

// 这些消息将被传递
subject.Notify("[IMPORTANT] Message 1");
subject.Notify("[IMPORTANT] Message 2");

// 取消订阅 observer2
subscription2.Dispose();

// 再次通知
subject.Notify("[IMPORTANT] Message 3");  // 只有 observer1 收到

// 错误通知(不过滤)
subject.NotifyError(new Exception("Critical Error"));

// 完成通知(不过滤)
subject.OnCompleted();

#endregion

#region Subject


public class FilteredObservable<T> : IObservable<T>
{
    private readonly List<IObserver<T>> _observers = new();
    private readonly Func<T, bool> _filter;
    private readonly object _lock = new();

    public FilteredObservable(Func<T, bool> filter)
    {
        _filter = filter ?? throw new ArgumentNullException(nameof(filter));
    }

    public IDisposable Subscribe(IObserver<T> observer)
    {
        lock (_lock)
        {
            if (!_observers.Contains(observer))
                _observers.Add(observer);
        }
        return new Unsubscriber(() =>
        {
            lock (_lock)
            {
                _observers.Remove(observer);
            }
        });
    }

    public async Task Notify(T value)
    {
        if (!_filter(value)) return;

        IObserver<T>[] observersCopy;
        lock (_lock)
        {
            observersCopy = _observers.ToArray();
        }

        foreach (var observer in observersCopy)
        {
            try
            {
                observer.OnNext(value);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Notification failed: {ex.Message}");
            }
        }
    }

    public async Task NotifyError(Exception error)
    {
        IObserver<T>[] observersCopy;
        lock (_lock)
        {
            observersCopy = _observers.ToArray();
            _observers.Clear();
        }

        foreach (var observer in observersCopy)
        {
            try
            { 
                observer.OnError(error);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error notification failed: {ex.Message}");
            }
        }
    }

    public async Task OnCompleted()
    {
        IObserver<T>[] observersCopy;
        lock (_lock)
        {
            observersCopy = _observers.ToArray();
            _observers.Clear();
        }

        foreach (var observer in observersCopy)
        {
            try
            {
                observer.OnCompleted();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Completion notification failed: {ex.Message}");
            }
        }
    }

    private class Unsubscriber : IDisposable
    {
        private readonly Action _unsubscribeAction;

        public Unsubscriber(Action unsubscribeAction) => _unsubscribeAction = unsubscribeAction;

        public void Dispose() => _unsubscribeAction?.Invoke();
    }
}

#endregion

#region Observer

public class Observer : IObserver<string>
{
    private readonly string _name;

    public Observer(string name)
    {
        _name = name;
    }

    public void OnCompleted()
    {
        Console.WriteLine($"{_name} completed.");
    }

    public void OnError(Exception error)
    {
        Console.WriteLine($"{_name} error: {error.Message}");
    }

    public void OnNext(string value)
    {
        Console.WriteLine($"{_name} received: {value}");
    }
}
#endregion
c# 复制代码
Observer 1 received: [IMPORTANT] Message 1
Observer 2 received: [IMPORTANT] Message 1
Observer 1 received: [IMPORTANT] Message 2
Observer 2 received: [IMPORTANT] Message 2
Observer 1 received: [IMPORTANT] Message 3
Observer 1 error: Critical Error

4.2.5 特点

  • 依赖框架:依赖于.NET框架,不适合跨平台或非.NET环境。
  • 学习曲线:需要一定的.NET框架知识才能熟练使用。
  • 与LINQ集成:可以使用LINQ查询语法对事件流进行操作,简化代码。
  • 性能优化:通过高效的订阅机制和事件分发,提升了性能。
  • 扩展性强:支持事件过滤、组合、转换等高级功能。
  • 线程安全:框架提供了线程安全的机制,减少了线程冲突的风险。
  • 标准化:基于.NET框架的标准接口,具有统一的规范。

4.2.6 适用场景

  • 复杂事件处理:适用于事件流复杂、需要高级操作(如过滤、组合、转换)的场景。
  • 多线程环境:在多线程或异步编程中,可以有效避免线程安全问题。
  • 数据流处理:适合处理数据流,如传感器数据、实时消息等。
  • 与.NET生态系统集成:与.NET的其他功能(如LINQ、Task并行库)无缝集成。

4.3 System.Reactive

System.Reactive 是基于IObservable<T>IObserver<T>的扩展库,用于处理事件流和异步数据流。

它将事件和数据流抽象为可观察序列,并通过 LINQ 风格的操作符实现订阅、过滤、转换和合并。

核心思想:使用响应式扩展库处理复杂事件流。Rx可以这样定义:Rx = Observables + LINQ + Schedulers。

4.3.1 安装

通过NuGet包管理器安装System.Reactive包

4.3.2 示例

c# 复制代码
using System.Reactive.Linq;
using System.Reactive.Subjects;

// 创建可观察序列
var subject = new Subject<string>();
var observable = subject.AsObservable();

// 订阅观察者
var subscription = observable
    .Where(msg => msg.StartsWith("IMPORTANT"))
    .Subscribe(msg => Console.WriteLine($"Rx received: {msg}"));

// 推送消息
subject.OnNext("IMPORTANT: System update");
subject.OnNext("Normal message");  // 被过滤

// 取消订阅
subscription.Dispose();

输出:

text 复制代码
Rx received: IMPORTANT: System update

4.3.3 特点

  • 强大的事件流处理(过滤、映射、合并等)
  • 支持LINQ查询操作
  • 异步事件处理支持
  • 自动管理资源释放(通过IDisposable)
  • 需引入第三方库(System.Reactive)
  • 学习曲线较陡

4.3.4 适用场景

  • 适合复杂事件流处理
  • 实时数据更新
  • 多线程和异步编程场景
  • 功能强大且与.NET生态系统无缝集成
  • 优先选择在需要复杂事件流处理的场景

五、使用场景

以下是观察者模式不同实现方式的对比总结:

实现方式 适合场景 选择建议
经典实现 适用于简单的事件通知场景,如 GUI 编程中组件间的交互。 当事件逻辑简单、不需要复杂的数据流处理时,适合使用经典实现。
委托与事件实现 适用于.NET中的事件处理,尤其是需要在多个组件或类之间传递事件的场景。 如果使用的是.NET框架,并且需要在类之间传递事件,委托与事件是首选。
`IObservable<T>`/`IObserver<T>`实现 适用于需要灵活处理数据流的场景,如异步数据处理、多线程环境下的事件推送。 当需要更灵活地控制数据流,或者需要支持多个观察者订阅时,`IObservable<T>`/`IObserver<T>`是一个不错的选择。
`System.Reactive`实现 适用于复杂的数据流处理,尤其是需要对事件进行转换、过滤、组合等操作的场景。 如果涉及复杂的数据流处理,或者需要响应式编程的支持,`System.Reactive`是最佳选择。

六、扩展

6.1 初始化和基础架构搭建

6.1.1 初始化和基础架构搭建

6.1.1.1 一个观察者观察多个目标
  1. 场景:有时观察者需要依赖多个目标对象。

  2. 问题:如果观察者无法区分通知的来源,导致无法针对不同目标对象做出准确响应。

    示例:假设一个用户界面中有一个观察者用于监控多个数据源(如温度、湿度和空气质量传感器)。

    当任何一个数据源更新时,观察者都会收到通知,但无法区分是哪个数据源发生了变化,从而无法针对性地更新界面元素。

  3. 改动:对目标对象的Update接口 进行扩展,确保观察者能够准确识别通知的来源

c# 复制代码
using System;
using System.Collections.Generic;

public class Subject
{
    private List<IObserver> observers = new List<IObserver>();

    // 注册观察者
    public void Attach(IObserver observer)
    {
        observers.Add(observer);
    }

    // 注销观察者
    public void Detach(IObserver observer)
    {
        observers.Remove(observer);
    }

    // 通知所有观察者
    public void NotifyObservers(string message)
    {
        foreach (var observer in observers)
        {
            observer.Update(this, message); // 将自己作为参数传递给观察者
        }
    }
}
6.1.1.2 目标多而观察者少
  • 问题

    在传统观察者模式中,目标对象(Subject)直接保存观察者的引用。这种方式简单直观,但当目标对象数量多而观察者数量少时,会产生明显弊端:

    • 存储开销问题

    每个目标对象都要分配存储空间保存观察者引用,即使某些目标对象没有观察者。

    这会导致大量不必要的存储开销,尤其在目标对象数量庞大时,开销更加显著。

    • 生命周期管理问题

      如果目标对象的生命周期较短,而观察者集合被强引用保留,可能会导致内存泄漏。

      因为即使目标对象被垃圾回收,观察者集合仍然占用内存,无法被清理。

  • 解决方案

  1. 使用HashSet<IObserver>
    为了解决存储开销问题,可以考虑使用HashSet<IObserver>来存储观察者。HashSet提供了高效的动态添加和移除操作,能够更好地支持观察者在运行时的动态变化。
  • 优点
    1. 动态管理观察者HashSet提供高效的动态添加和移除操作,支持观察者在运行时的动态变化。
    2. 避免重复存储HashSet自动去重,避免了重复存储相同的观察者。
    3. 提高存储效率:通过哈希表实现快速查找和插入操作,减少了存储和检索观察者的开销。
  • 缺点
    1. 内存泄漏风险 :如果目标对象被垃圾回收,但观察者集合(HashSet<IObserver>)仍然存在引用,那么这些观察者可能不会被正确清理,从而导致内存泄漏。
    2. 生命周期管理复杂:需要手动管理目标对象和观察者集合的生命周期,确保在目标对象被销毁时,观察者集合也被正确清理。
  1. 使用ConditionalWeakTable
    ConditionalWeakTable是一种特殊的哈希表,它允许键(目标对象)在没有其他强引用时被垃圾回收,而不会影响值(观察者集合)的存在。

通过ConditionalWeakTable,只有真正有观察者的目标对象才会占用存储空间,同时避免了内存泄漏问题。

以下是使用ConditionalWeakTable实现观察者模式的示例代码:

c# 复制代码
using System.Runtime.CompilerServices;
using System.Collections.Generic;

// 目标和观察者接口省略...

public class Subject : ISubject
{
    private static readonly object _syncLock = new object();
    private static readonly ConditionalWeakTable<Subject, HashSet<IObserver>> observerMap = 
        new ConditionalWeakTable<Subject, HashSet<IObserver>>();

    public void Attach(IObserver observer)
    {
        lock (_syncLock)
        {
            if (!observerMap.TryGetValue(this, out var observers))
            {
                observers = new HashSet();
                observerMap.Add(this, observers);
            }
            observers.Add(observer);
        }
    }

    public void Detach(IObserver observer)
    {
        lock (_syncLock)
        {
            if (observerMap.TryGetValue(this, out var observers))
            {
                observers.Remove(observer);
                if (observers.Count == 0)
                {
                    observerMap.Remove(this);
                }
            }
        }
    }

    public void NotifyObservers(string message)
    {
        HashSet<IObserver> observersCopy;
        lock (_syncLock)
        {
            if (!observerMap.TryGetValue(this, out var observers))
            {
                return;
            }
            observersCopy = new HashSet(observers);
        }

        foreach (var observer in observersCopy)
        {
            observer.Update(this, message);
        }
    }
}
  • 优点

    1. 避免内存泄漏ConditionalWeakTable允许目标对象在没有其他强引用时被垃圾回收,从而避免了内存泄漏问题。
    2. 动态管理 :目标对象和观察者之间的关系是动态的,ConditionalWeakTable提供了一种灵活的方式来管理这种关系。
    3. 优化存储效率:只有真正有观察者的目标对象才会占用存储空间,减少了不必要的存储开销。
  • 缺点

    1. 性能开销ConditionalWeakTable的查找和管理操作比直接使用HashSet<IObserver>更复杂,可能会引入额外的性能开销。
    2. 复杂性增加 :代码的复杂性增加,需要理解ConditionalWeakTable的工作机制。
  • 实际应用中的权衡

    观察者模式在实际应用中需要根据具体需求选择合适的实现方式。

    如果目标对象数量较多且生命周期较短,推荐使用ConditionalWeakTable,以避免内存泄漏并优化存储效率。

    如果目标对象生命周期较长且观察者管理较为简单,则可以直接使用HashSet<IObserver>,以简化实现和提高性能。

6.1.1.3 目标对象之间存在复杂依赖关系
  • 问题

    当目标对象存在复杂依赖关系时,直接通知观察者可能引发以下问题:

    1. 多次更新:观察者可能因多个目标对象的改变收到重复通知,导致冗余操作。
    2. 更新顺序问题:目标对象的改变顺序可能导致观察者在状态未完全更新时收到通知,获取不一致的状态。
    3. 维护成本高:复杂的依赖关系增加了代码复杂性和维护难度。
  • 解决方案

    引入一个独立的更改管理器(ChangeManager) 来封装和管理复杂的更新逻辑。

  • 更改管理器的作用

    1. 维护映射关系:管理目标对象与观察者的映射,降低耦合度。
    2. 定义更新策略:在所有相关目标对象状态更新完毕后统一通知观察者,避免冗余和不一致问题。
    3. 优化更新逻辑:根据依赖关系优化更新流程,确保观察者只接收一次更新。
  • 更改管理器的两种实现

    是一个典型的中介者模式实例,通常以单例模式全局可见,从而确保整个系统中只有一个协调中心。两种特殊的更改管理器实现:

  • SimpleChangeManager

  • 实现方案

    • 使用字典维护目标对象与观察者的映射
    • 使用脏标记集合跟踪需要更新的目标对象
    • Commit时统一通知观察者并去重
  • 示例

c# 复制代码
      using System;
      using System.Collections.Generic;
      using System.Linq;

      #region Client Code

      // 使用简单更改管理器
      ChangeManager.Instance = SimpleChangeManager.Instance;
              
      var subject = new ConcreteSubject();
      var observer = new ConcreteObserver();
              
      ChangeManager.Instance.Register(subject, observer);
              
      subject.State = 42;
      subject.Notify();
      ChangeManager.Instance.Commit();
              
      // 使用DAG更改管理器
      ChangeManager.Instance = DAGChangeManager.Instance;
      var subjectA = new ConcreteSubject();
      var subjectB = new ConcreteSubject();
      var dagObserver = new ConcreteObserver();
              
      ChangeManager.Instance.Register(subjectA, dagObserver);
      ChangeManager.Instance.Register(subjectB, dagObserver);
      ((DAGChangeManager)ChangeManager.Instance).AddDependency(subjectA, subjectB);
              
      subjectA.State = 10;
      subjectB.State = 20;
      subjectA.Notify();
      subjectB.Notify();
      ChangeManager.Instance.Commit();
      #endregion

      #region IObserver

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


      #endregion

      #region IObservable
      // 目标对象接口
      public interface ISubject
      {
          void Notify();
      }
      #endregion

      #region ChangeManager

      // 更改管理器抽象类
      public abstract class ChangeManager
      {
          public static ChangeManager Instance { get;  set; }

          public abstract void Register(ISubject subject, IObserver observer);
          public abstract void Unregister(ISubject subject, IObserver observer);
          public abstract void Notify(ISubject subject);
          public abstract void Commit();
      }

      #endregion

      #region SimpleChangeManager

      // 简单更改管理器实现
      public sealed class SimpleChangeManager : ChangeManager
      {
          private readonly Dictionary> _observers = new();
          private readonly HashSet<ISubject> _dirtySubjects = new();

          static SimpleChangeManager()
          {
          }
          private static SimpleChangeManager _instance;
          public  static SimpleChangeManager Instance
          {
              get
              {
                  if (_instance == null)
                  {
                      _instance = new SimpleChangeManager();
                  }
                  return _instance;
              }
          }

          public override void Register(ISubject subject, IObserver observer)
          {
              if (!_observers.ContainsKey(subject))
              {
                  _observers[subject] = new HashSet();
              }
              _observers[subject].Add(observer);
          }

          public override void Unregister(ISubject subject, IObserver observer)
          {
              if (_observers.TryGetValue(subject, out var observers))
              {
                  observers.Remove(observer);
              }
          }

          public override void Notify(ISubject subject)
          {
              lock (_dirtySubjects)
              {
                  _dirtySubjects.Add(subject);
              }
          }

          public override void Commit()
          {
              HashSet<IObserver> notified = new();
              List<ISubject> toProcess;

              lock (_dirtySubjects)
              {
                  toProcess = _dirtySubjects.ToList();
                  _dirtySubjects.Clear();
              }

              foreach (var subject in toProcess)
              {
                  if (_observers.TryGetValue(subject, out var observers))
                  {
                      foreach (var observer in observers.Where(observer => notified.Add(observer)))
                      {
                          observer.Update(subject);
                      }
                  }
              }
          }
      }

      #endregion


      #region ConcreteSubject

      // 示例使用
      public class ConcreteSubject : ISubject
      {
          public int State { get; set; }

          public void Notify()
          {
              ChangeManager.Instance.Notify(this);
          }
      }


      #endregion

      #region ConcreteObserver
      public class ConcreteObserver : IObserver
      {
          public void Update(ISubject subject)
          {
              if (subject is ConcreteSubject concreteSubject)
              {
                  Console.WriteLine($"Received update: {concreteSubject.State}");
              }
          }
      }
      #endregion
      ```

 ```c#
      Received update: 42
  • 特点:总是更新每个目标对象的所有观察者。实现简单,易于理解。可能会导致冗余更新,效率较低。

  • 适用场景:当目标对象之间没有复杂的依赖关系,或者更新逻辑简单时,这种实现方式比较合适。

  • DAGChangeManager

  • 实现方案

  • 继承简单管理器基础功能

  • 添加依赖关系管理

  • 使用拓扑排序确保更新顺序

    1. 使用深度优先搜索(DFS)。

    2. 先处理依赖项,再处理当前主题。

    3. 最终得到的排序结果是一个线性顺序,满足所有依赖关系

  • 示例

c 复制代码
      #region DAGChangeManager

      // 基于DAG的复杂更改管理器
      public sealed class DAGChangeManager : ChangeManager
      {
          private readonly Dictionary> _observers = new();
          private readonly Dictionary<ISubject, HashSet<ISubject>> _dependencies = new();
          private readonly HashSet<ISubject> _dirtySubjects = new();

          static DAGChangeManager()
          {
             
          }
          private static DAGChangeManager _instance;
          public  static DAGChangeManager Instance
          {
              get
              {
                  if (_instance == null)
                  {
                      _instance = new DAGChangeManager();
                  }
                  return _instance;
              }
          }

          // 添加依赖关系(dependent 依赖于 dependency)
          public void AddDependency(ISubject dependent, ISubject dependency)
          {
              if (!_dependencies.ContainsKey(dependent))
              {
                  _dependencies[dependent] = new HashSet();
              }
              _dependencies[dependent].Add(dependency);
          }

          public override void Register(ISubject subject, IObserver observer)
          {
              if (!_observers.ContainsKey(subject))
              {
                  _observers[subject] = new HashSet();
              }
              _observers[subject].Add(observer);
          }

          public override void Unregister(ISubject subject, IObserver observer) 
          {
              if (_observers.TryGetValue(subject, out var observers))
              {
                  observers.Remove(observer);
              }
          }

          public override void Notify(ISubject subject)
          {
              lock (_dirtySubjects)
              {
                  _dirtySubjects.Add(subject);
              }
          }

          public override void Commit()
          {
              List<ISubject> processingOrder;
              lock (_dirtySubjects)
              {
                  processingOrder = TopologicalSort(_dirtySubjects);
                  _dirtySubjects.Clear();
              }

              HashSet<IObserver> notified = new();
              foreach (var subject in processingOrder)
              {
                  if (_observers.TryGetValue(subject, out var observers))
                  {
                      foreach (var observer in observers.Where(observer => notified.Add(observer)))
                      {
                          observer.Update(subject);
                      }
                  }
              }
          }

          private List<ISubject> TopologicalSort(HashSet<ISubject> subjects)
          {
              var sorted = new List<ISubject>();
              var visited = new HashSet<ISubject>();

              foreach (var subject in subjects.OrderBy(s => s.GetHashCode()))
              {
                  Visit(subject, visited, sorted);
              }

              return sorted;
          }

          private void Visit(ISubject subject, HashSet<ISubject> visited, List<ISubject> sorted)
          {
              if (!visited.Add(subject)) return;

              if (_dependencies.TryGetValue(subject, out var dependencies))
              {
                  foreach (var dependency in dependencies)
                  {
                      Visit(dependency, visited, sorted);
                  }
              }

              sorted.Add(subject);
          }
      }



      #endregion

      ```

 ```c#
      Received update: 20
复制代码
- **特点**:处理目标对象及其观察者之间依赖关系构成的**无环有向图(DAG,Directed Acyclic Graph)**。
  • 优点:可以避免冗余更新,确保观察者只接收一次更新。

  • 缺点:实现复杂度较高,需要维护依赖关系图。

  • 适用场景:当观察者可能依赖多个目标对象,且目标对象之间存在复杂的依赖关系时,这种实现方式更好。

  • 总结

    更改管理器(ChangeManager)是一种优化机制,用于封装复杂更新逻辑,简化目标对象与观察者之间的依赖关系。它通过以下方式实现优化:

    1. 职责分离:将映射关系和更新逻辑封装到独立对象中。
    2. 统一通知:在所有目标对象状态更新完毕后,一次性通知观察者。
    3. 优化策略 :避免冗余更新。
      更改管理器可以是简单的SimpleChangeManager 或复杂的DAGChangeManager,具体取决于系统需求。它通常以单例模式全局可见,作为系统的协调中心。

6.2 注册机制

6.2.1 问题

在观察者模式中,传统的事件通知机制可能存在以下问题:

  1. 通知效率低:目标对象可能向所有观察者发送通知,即使某些观察者并不关心某些事件。
  2. 耦合度高:观察者与目标对象之间的依赖关系较强,难以灵活调整。
  3. 缺乏灵活性:观察者无法动态选择关注的事件类型,难以适应复杂的应用场景。

这些问题可能导致系统性能下降,代码难以维护和扩展。

6.2.2 解决方案

通过显式注册机制,观察者可以明确指定其感兴趣的事件类型,目标对象仅向已注册的观察者发送相关通知。

6.2.2.1 实现思路
  1. 引入"方面(Aspect)"概念:将目标对象的状态变化分解为多个独立的方面,每个方面代表一种特定类型的变更。
  2. 观察者选择性注册:观察者可以根据需要注册对特定方面的兴趣,从而只接收关注的事件通知。
  3. 目标对象优化通知:目标对象仅向已注册特定方面的观察者发送通知,避免不必要的消息传递。
6.2.2.2 示例
  • 定义方面(Aspect)枚举

    方面(Aspect)枚举:定义了目标对象可能的状态变化类型,例如状态变化、数据更新和错误发生。

    c# 复制代码
    // 定义方面(Aspect)枚举,表示目标对象可能的状态变化类型
    public enum Aspect
    {
        StateChange,
        DataUpdate,
        ErrorOccurred
    }
  • 目标对象类 Subject

    • 使用字典存储每个方面对应的观察者列表。
    • 提供注册和取消注册的方法,允许观察者显式指定感兴趣的方面。
    • 提供通知方法,仅向注册了特定方面的观察者发送通知。
    c# 复制代码
    // 目标对象类
    public class Subject
    {
        // 用于存储观察者订阅的方面
        private Dictionary>> observers = new Dictionary>>();
    
        public Subject()
        {
            // 初始化方面列表
            foreach (Aspect aspect in Enum.GetValues(typeof(Aspect)))
            {
                observers[aspect] = new List>();
            }
        }
    
        // 注册观察者
        public void RegisterObserver(Aspect aspect, Action observer)
        {
            observers[aspect].Add(observer);
            Console.WriteLine($"Observer registered for aspect: {aspect}");
        }
    
        // 取消注册观察者
        public void UnregisterObserver(Aspect aspect, Action observer)
        {
            observers[aspect].Remove(observer);
            Console.WriteLine($"Observer unregistered from aspect: {aspect}");
        }
    
        // 通知观察者
        public void NotifyObservers(Aspect aspect, string message)
        {
            if (observers[aspect].Count > 0)
            {
                Console.WriteLine($"Notifying observers for aspect: {aspect}");
                foreach (var observer in observers[aspect])
                {
                    observer(message);
                }
            }
            else
            {
                Console.WriteLine($"No observers registered for aspect: {aspect}");
            }
        }
    
        // 模拟目标对象状态变化
        public void ChangeState()
        {
            Console.WriteLine("Subject state changed.");
            NotifyObservers(Aspect.StateChange, "State has changed.");
        }
    
        public void UpdateData()
        {
            Console.WriteLine("Subject data updated.");
            NotifyObservers(Aspect.DataUpdate, "Data has been updated.");
        }
    
        public void ErrorOccurred()
        {
            Console.WriteLine("Error occurred in the subject.");
            NotifyObservers(Aspect.ErrorOccurred, "An error has occurred.");
        }
    }
  • 观察者类 Observer

    • 包含多个回调方法,分别对应不同的方面。
    • 观察者可以根据需要注册对特定方面的兴趣。
    c# 复制代码
    // 观察者类
    public class Observer
    {
        private string name;
    
        public Observer(string name)
        {
            this.name = name;
        }
    
        public void OnStateChange(string message)
        {
            Console.WriteLine($"{name} received state change notification: {message}");
        }
    
        public void OnDataUpdate(string message)
        {
            Console.WriteLine($"{name} received data update notification: {message}");
        }
    
        public void OnError(string message)
        {
            Console.WriteLine($"{name} received error notification: {message}");
        }
    }
  • Client

    • 创建目标对象和观察者。
    • 观察者显式注册对特定方面的兴趣。
    • 模拟目标对象的状态变化,观察通知机制的运行。
    c# 复制代码
    // 测试程序
    public class Program
    {
        public static void Main()
        {
            // 创建目标对象
            Subject subject = new Subject();
    
            // 创建观察者
            Observer observer1 = new Observer("Observer1");
            Observer observer2 = new Observer("Observer2");
    
            // 观察者1注册对所有方面的兴趣
            subject.RegisterObserver(Aspect.StateChange, observer1.OnStateChange);
            subject.RegisterObserver(Aspect.DataUpdate, observer1.OnDataUpdate);
            subject.RegisterObserver(Aspect.ErrorOccurred, observer1.OnError);
    
            // 观察者2仅注册对错误方面的兴趣
            subject.RegisterObserver(Aspect.ErrorOccurred, observer2.OnError);
    
            // 模拟目标对象状态变化
            subject.ChangeState();
            subject.UpdateData();
            subject.ErrorOccurred();
    
            // 观察者2取消对错误方面的兴趣
            subject.UnregisterObserver(Aspect.ErrorOccurred, observer2.OnError);
    
            // 再次触发错误事件,观察者2不再接收通知
            subject.ErrorOccurred();
        }
    }
  • 结果

    c# 复制代码
    Observer registered for aspect: StateChange
    Observer registered for aspect: DataUpdate
    Observer registered for aspect: ErrorOccurred
    Observer registered for aspect: ErrorOccurred
    Subject state changed.
    Notifying observers for aspect: StateChange
    Observer1 received state change notification: State has changed.
    Subject data updated.
    Notifying observers for aspect: DataUpdate
    Observer1 received data update notification: Data has been updated.
    Error occurred in the subject.
    Notifying observers for aspect: ErrorOccurred
    Observer1 received error notification: An error has occurred.
    Observer2 received error notification: An error has occurred.
    Observer unregistered from aspect: ErrorOccurred
    Error occurred in the subject.
    Notifying observers for aspect: ErrorOccurred
    Observer1 received error notification: An error has occurred.
6.2.2.3 优点
  1. 提高通知效率:目标对象仅发送观察者关心的事件。
  2. 降低耦合度:观察者与目标对象之间保持松散耦合。
  3. 灵活性强:观察者可根据需求动态调整关注的事件类型。
6.2.2.4 适用场景

这种方法通过事件驱动机制实现了高效的通信,适用于需要灵活配置和优化性能的场景,例如:

  • 多用户系统中对不同事件的关注。
  • 复杂系统的状态监控和事件响应。
  • 需要动态调整事件监听的应用。

6.3 触发机制

6.3.1 触发之前

在通知观察者之前,目标对象的状态必须是完整且正确的,否则观察者可能会基于错误的状态进行操作,从而引发问题。

6.3.2 更新的触发者

目标对象与观察者通过通知机制保持同步,存在以下两种触发方式:

  1. 自动触发 :目标对象状态变化后自动执行Notify。优势在于无需客户手动操作,但可能因连续操作频繁触发更新,影响效率。
  2. 手动触发 :客户在状态变化完成后适时调用Notify。优点在于可避免不必要的中间更新,但增加了客户操作负担,且存在因遗漏调用而导致错误的风险。

6.3.3 示例

6.3.3.1 抽象目标对象
  • observers:存储观察者的集合。
  • mainState secondaryState:主题的状态信息。
  • Attach Detach:用于添加和移除观察者。
  • Notify:抽象方法,由具体主题类实现,用于通知观察者。
  • PrepareUpdate:用于准备状态更新,但不触发通知。
  • ShowState:用于打印当前状态。
c# 复制代码
#region abstract Subject

// 抽象目标对象
abstract class Subject
{
    protected HashSet<IObserver> observers = new HashSet<IObserver>();
    protected int mainState;
    protected int secondaryState;

    public void Attach(IObserver observer) => observers.Add(observer);
    public void Detach(IObserver observer) => observers.Remove(observer);
    public abstract void Notify();

    protected void PrepareUpdate(int main, int secondary)
    {
        mainState = main;
        secondaryState = secondary;
    }

    public void ShowState()
    {
        Console.WriteLine($"State: [{mainState}, {secondaryState}]");
    }
}

#endregion
6.3.3.2 观察者接口
c# 复制代码
#region IObserver

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

#endregion
6.3.3.3 具体的的观察者
c# 复制代码
#region ConcreteObservers

// 具体观察者
class StateObserver : IObserver
{
    private readonly Subject subject;

    public StateObserver(Subject subject)
    {
        this.subject = subject;
    }

    public void Update()
    {
        Console.Write("Observer received update: ");
        subject.ShowState();
    }
}

#endregion
6.3.3.4 自动触发的目标对象

状态发生改变时,自动触发通知

c# 复制代码
#region AutoTriggerSubject

// 自动触发实现
class AutoTriggerSubject : Subject
{
    public void SetMainState(int value)
    {
        mainState = value;
        Notify(); // 自动触发
    }

    public override void Notify()
    {
        Console.WriteLine("[AutoTrigger] Notifying observers...");
        foreach (var observer in observers)
        {
            observer.Update();
        }
    }
}

#endregion
6.3.3.5 手动触发的目标对象
c# 复制代码
#region ManualTriggerSubject

// 手动触发实现
class ManualTriggerSubject : Subject
{
    public void CompleteUpdate(int main, int secondary)
    {
        PrepareUpdate(main, secondary);
        // 不自动触发
    }

    public override void Notify()
    {
        Console.WriteLine("[ManualTrigger] Notifying observers...");
        foreach (var observer in observers)
        {
            observer.Update();
        }
    }
}
#endregion
6.3.3.6 使用示例
c# 复制代码
#region  Client Code

// 自动触发演示
Console.WriteLine("=== Automatic Trigger Demo ===");
var autoSubject = new AutoTriggerSubject();
var obs1 = new StateObserver(autoSubject);
autoSubject.Attach(obs1);

autoSubject.SetMainState(10);  // 触发通知
autoSubject.SetMainState(20);  // 再次触发

// 手动触发演示
Console.WriteLine("\n=== Manual Trigger Demo ===");
var manualSubject = new ManualTriggerSubject();
var obs2 = new StateObserver(manualSubject);
manualSubject.Attach(obs2);

manualSubject.CompleteUpdate(1, 100);
manualSubject.CompleteUpdate(2, 200);
manualSubject.CompleteUpdate(3, 300);
manualSubject.Notify();  // 单次触发

#endregion

结果

c# 复制代码
=== Automatic Trigger Demo ===
[AutoTrigger] Notifying observers...
Observer received update: State: [10, 0]
[AutoTrigger] Notifying observers...
Observer received update: State: [20, 0]

=== Manual Trigger Demo ===
[ManualTrigger] Notifying observers...
Observer received update: State: [3, 300]

6.4 信息传递机制

观察者模式中目标将这些信息作为Update操作的一个参数传递出去。这些信息的量可能很小,也可能很大。其信息量传递大小的两个极端便是:推模型(Push Model)和拉模型(Pull Model)。

推模型:一种主动推送信息的机制,主题对象在状态发生变化时,主动将包含具体变更信息的参数推送给所有观察者,观察者通过更新方法来接收这些信息。这种方式传递的是结构化数据,适用于需要接收完整、结构化数据且数据格式相对稳定的场景。

拉模型:一种按需获取信息的机制,主题对象在状态发生变化时,仅发送一个简单的通知给观察者,观察者需要主动调用主题对象的查询接口来获取所需的信息。这种方式更加灵活,适用于观察者需要不同数据子集或数据格式可能频繁变化的场景。

6.4.1 解决方案与实现方式

6.4.1.1 推模型(Push Model)
  • 实现原理

    目标对象主动推送包含具体变更信息的参数至观察者的更新方法,采用事件驱动机制传递结构化数据。

  • 典型实现示例(气象监测系统)

    • IObserver 接口

      c# 复制代码
      #region IObserver 接口
      // 观察者接口
      public interface IObserver
      {
          void Update(WeatherData data);
      }
      #endregion
    • 具体观察者 :Display 类

      c# 复制代码
      #region 具体观察者 :Display 类
      // 具体观察者
      public class Display : IObserver
      {
          public void Update(WeatherData data)
          {
              Console.WriteLine($"当前温度:{data.Temperature}℃");
          }
      }
      #endregion
    • Subject 类: WeatherStation 类

      • SetMeasurements(float temp):设置新的温度数据,并触发通知。
      • NotifyObservers(WeatherData data):遍历观察者列表,调用每个观察者的Update方法。
      c# 复制代码
      #region  Subject 类: WeatherStation 类
      
      // 目标对象
      public class WeatherStation
      {
          private List<IObserver> _observers = new List<IObserver>();
          private float _temperature;
      
          public void SetMeasurements(float temp)
          {
              _temperature = temp;
              NotifyObservers(new WeatherData(temp));
          }
      
          private void NotifyObservers(WeatherData data)
          {
              foreach (var observer in _observers)
              {
                  observer.Update(data);
              }
          }
      
          public void Attach(IObserver observer) => _observers.Add(observer);
          public void Detach(IObserver observer) => _observers.Remove(observer);
      }
      
      // 数据传输对象
      public class WeatherData
      {
          public float Temperature { get; }
      
          public WeatherData(float temperature)
          {
              Temperature = temperature;
          }
      }
      
      #endregion
    • Client

      c# 复制代码
      #region Client Code
      
      // 创建一个天气站对象
      WeatherStation weatherStation = new WeatherStation();
      
      // 创建两个观察者对象
      Display display1 = new Display();
      Display display2 = new Display();
      
      // 将观察者添加到天气站的观察者列表
      weatherStation.Attach(display1);
      weatherStation.Attach(display2);
      
      // 模拟天气站更新数据
      Console.WriteLine("天气站更新温度为 25.5℃:");
      weatherStation.SetMeasurements(25.5f);
      
      // 移除一个观察者
      weatherStation.Detach(display1);
      
      // 再次更新数据
      Console.WriteLine("\n天气站更新温度为 28.0℃:");
      weatherStation.SetMeasurements(28.0f);
      
      Console.ReadLine();
      
      #endregion

      结果

      c# 复制代码
      天气站更新温度为 25.5℃:
      当前温度:25.5℃
      当前温度:25.5℃
      
      天气站更新温度为 28.0℃:
      当前温度:28℃
  • 适用场景

    • 观察者需要接收完整、结构化的数据
    • 数据格式相对稳定且预先可知
    • 需要最小化观察者的查询操作
    • 实时性要求高于通信成本
6.4.1.2 拉模型(Pull Model)
  • 实现原理

    目标对象仅发送简单通知,观察者主动调用目标对象的查询接口获取所需信息,实现按需获取

  • 典型实现示例

    假设一个股票市场监控系统,目标对象是股票市场,观察者是不同的投资者。当股票价格发生变化时,股票市场仅通知投资者价格发生了变化,而投资者需要主动查询当前的股票价格。

    • 目标和观察者接口

      c# 复制代码
      #region Interface Code
      
      // 观察者接口
      public interface IObserver
      {
          void Update();
      }
      
      // 目标接口
      public interface ISubject
      {
          void Attach(IObserver observer);
          void Detach(IObserver observer);
          void Notify();
      }
      
      #endregion
    • 具体目标

      c# 复制代码
      #region Concrete Subject : StockMarket
      
      // 具体目标类
      public class StockMarket : ISubject
      {
          private List<IObserver> observers = new List<IObserver>();
          private decimal stockPrice;
      
          public decimal StockPrice
          {
              get { return stockPrice; }
              set
              {
                  stockPrice = value;
                  Notify();
              }
          }
      
          public void Attach(IObserver observer)
          {
              observers.Add(observer);
          }
      
          public void Detach(IObserver observer)
          {
              observers.Remove(observer);
          }
      
          public void Notify()
          {
              foreach (var observer in observers)
              {
                  observer.Update();
              }
          }
      }
      
      #endregion
    • 具体观察者

      通过Update方法接收通知,并从目标对象中获取当前股票价格。

      c# 复制代码
      #region Concrete Observer : Investor
      // 具体观察者类
      public class Investor : IObserver
      {
          private string name;
          private ISubject stockMarket;
      
          public Investor(string name, ISubject stockMarket)
          {
              this.name = name;
              this.stockMarket = stockMarket;
          }
      
          public void Update()
          {
              decimal currentPrice = ((StockMarket)stockMarket).StockPrice;
              Console.WriteLine($"{name} received notification. Current stock price: {currentPrice:C}");
          }
      }
      
      #endregion
    • 使用示例

      c# 复制代码
      #region Client Code
      
      // 创建股票市场对象
      StockMarket stockMarket = new StockMarket();
      
      // 创建投资者
      Investor investor1 = new Investor("Alice", stockMarket);
      Investor investor2 = new Investor("Bob", stockMarket);
      
      // 将投资者添加到股票市场的观察者列表
      stockMarket.Attach(investor1);
      stockMarket.Attach(investor2);
      
      // 模拟股票价格变化
      Console.WriteLine("Stock price changes to $100.");
      stockMarket.StockPrice = 100;
      
      Console.WriteLine("Stock price changes to $120.");
      stockMarket.StockPrice = 120;
      
      // 移除一个投资者
      stockMarket.Detach(investor1);
      
      Console.WriteLine("Stock price changes to $150.");
      stockMarket.StockPrice = 150;
      
      Console.ReadLine();
      #endregion

      结果

      c# 复制代码
      Stock price changes to $100.
      Alice received notification. Current stock price: ¥100.00
      Bob received notification. Current stock price: ¥100.00
      Stock price changes to $120.
      Alice received notification. Current stock price: ¥120.00
      Bob received notification. Current stock price: ¥120.00
      Stock price changes to $150.
      Bob received notification. Current stock price: ¥150.00
  • 适用场景

    • 适用于观察者需要不同数据子集的情况。
    • 数据格式可能频繁变化,观察者可以根据需要动态获取数据。
    • 通信成本不是主要瓶颈,因为观察者主动查询数据。
6.4.1.3 设计对比与权衡
维度 推模型 拉模型
耦合度 高(需预知观察者需求) 低(观察者自主决定获取内容)
通信效率 高(单次传输完整数据) 低(需多次请求-响应)
接口稳定性 要求高(参数结构需固定) 要求低(仅需保持查询接口)
可扩展性 较差(新增观察者类型需修改接口) 较好(新观察者可自主获取数据)

通过理解两种模型的本质特征,可以根据具体业务需求、系统约束和演进方向,制定出最适合当前上下文的信息传递策略。

在分布式系统和微服务架构中,这种设计权衡往往直接影响系统的可维护性和扩展能力。

6.4.1.4 最佳实践建议
  • 混合模式设计

    结合两种模型的优势,推送基础变更通知,允许观察者拉取补充信息。

    • 观察者接口

      c# 复制代码
      #region Interface Code
      
      public interface IObserver
      {
          void Update(int state);  // 接收基础状态
      }
      
      #endregion
    • 具体目标

      c# 复制代码
      #region Subject
      public class Subject
      {
          private List<IObserver> observers = new List<IObserver>();
          private int state;  // 被观察者的状态
      
          public int State
          {
              get { return state; }
              set
              {
                  state = value;
                  NotifyObservers();  // 状态改变时通知观察者
              }
          }
      
          // 注册观察者
          public void Attach(IObserver observer)
          {
              observers.Add(observer);
          }
      
          // 移除观察者
          public void Detach(IObserver observer)
          {
              observers.Remove(observer);
          }
      
          // 通知观察者
          public void NotifyObservers()
          {
              foreach (IObserver observer in observers)
              {
                  observer.Update(state);  // 推送基础状态
              }
          }
      }
      
      #endregion
    • 具体观察者:ConcreteObserver

      c# 复制代码
      #region ConcreteObserver
      
      
      public class ConcreteObserver : IObserver
      {
          private Subject subject;  // 持有被观察者的引用
      
          public ConcreteObserver(Subject subject)
          {
              this.subject = subject;
              subject.Attach(this);  // 注册到被观察者
          }
      
          public void Update(int state)
          {
              Console.WriteLine($"Received base state: {state}");
      
              // 根据需要拉取补充信息
              if (state > 10)
              {
                  int additionalInfo = subject.State;  // 拉取补充信息
                  Console.WriteLine($"Additional info: {additionalInfo}");
              }
          }
      }
      
      #endregion
    • 使用示例:Client

      c# 复制代码
      #region Client Code
      
      Subject subject = new Subject();
      
      // 创建观察者并注册到被观察者
      ConcreteObserver observer1 = new ConcreteObserver(subject);
      ConcreteObserver observer2 = new ConcreteObserver(subject);
      
      // 改变被观察者的状态
      Console.WriteLine("Setting state to 5:");
      subject.State = 5;  // 输出基础状态
      
      Console.WriteLine("\nSetting state to 15:");
      subject.State = 15; // 输出基础状态和补充信息
      
      #endregion

      结果

      c# 复制代码
      Setting state to 5:
      Received base state: 5
      Received base state: 5
      
      Setting state to 15:
      Received base state: 15
      Additional info: 15
      Received base state: 15
      Additional info: 15
  • 通信优化策略

    实现思路

    1. 批量处理拉取请求 :通过在Subject中维护一个队列,将观察者的拉取请求批量处理。
    2. 建立数据缓存机制 :在Subject中缓存状态信息,避免重复计算或重复拉取。
    3. 使用差异更新(delta update):仅推送状态变化的部分,而不是完整状态。
    • 观察者接口 IObserver

      c# 复制代码
      #region Interface Code
      
      public interface IObserver
      {
          void Update(int state);  // 接收基础状态
      }
      
      #endregion
    • 目标对象 Subject

      c# 复制代码
      #region Subject
      
      public class Subject
      {
          private List<IObserver> observers = new List<IObserver>();
          private int state;  // 被观察者的状态
          private int previousState;  // 上一次的状态,用于差异更新
      
          public int State
          {
              get { return state; }
              set
              {
                  if (state != value)  // 检查状态是否变化
                  {
                      previousState = state;  // 保存上一次状态
                      state = value;
                      NotifyObservers();  // 状态改变时通知观察者
                  }
              }
          }
      
          // 注册观察者
          public void Attach(IObserver observer)
          {
              observers.Add(observer);
          }
      
          // 移除观察者
          public void Detach(IObserver observer)
          {
              observers.Remove(observer);
          }
      
          // 通知观察者
          public void NotifyObservers()
          {
              foreach (IObserver observer in observers)
              {
                  observer.Update(state);  // 推送基础状态
              }
          }
      
          // 提供拉取补充信息的接口
          public int GetAdditionalInfo()
          {
              // 模拟缓存机制:如果状态未变,直接返回缓存值
              if (state == previousState)
              {
                  Console.WriteLine("Using cached additional info.");
                  return previousState;
              }
      
              // 模拟拉取补充信息
              Console.WriteLine("Fetching additional info...");
              return state;
          }
      }
      
      #endregion
    • 具体观察者:ConcreteObserver

      c# 复制代码
      #region ConcreteObserver
      
      public class ConcreteObserver : IObserver
      {
          private Subject subject;  // 持有被观察者的引用
      
          public ConcreteObserver(Subject subject)
          {
              this.subject = subject;
              subject.Attach(this);  // 注册到被观察者
          }
      
          public void Update(int state)
          {
              Console.WriteLine($"Received base state: {state}");
      
              // 根据需要拉取补充信息
              if (state > 10)
              {
                  int additionalInfo = subject.GetAdditionalInfo();  // 拉取补充信息
                  Console.WriteLine($"Additional info: {additionalInfo}");
              }
          }
      }
      
      #endregion
    • 使用示例 Client

      c# 复制代码
      #region Client Code
      
      Subject subject = new Subject();
      
      // 创建观察者并注册到被观察者
      ConcreteObserver observer1 = new ConcreteObserver(subject);
      ConcreteObserver observer2 = new ConcreteObserver(subject);
      
      // 改变被观察者的状态
      Console.WriteLine("Setting state to 5:");
      subject.State = 5;  // 输出基础状态
      
      Console.WriteLine("\nSetting state to 15:");
      subject.State = 15; // 输出基础状态和补充信息
      
      Console.WriteLine("\nSetting state to 15 again (cached info will be used):");
      subject.State = 15; // 使用缓存信息
      #endregion

      结果

      c# 复制代码
      Setting state to 5:
      Received base state: 5
      Received base state: 5
      
      Setting state to 15:
      Received base state: 15
      Fetching additional info...
      Additional info: 15
      Received base state: 15
      Fetching additional info...
      Additional info: 15
      
      Setting state to 15 again (cached info will be used):

6.5 资源管理和错误处理

避免已删除目标的悬挂引用

删除目标时,需确保其观察者中不遗留对该目标的无效引用。

否则,当观察者尝试访问已销毁的目标时,可能会引发错误或异常,导致程序崩溃或行为不可预测。

为了避免这种情况,可以在目标对象被销毁之前,主动通知所有观察者解除对其的订阅。

示例

  • 目标接口和观察者接口

    c# 复制代码
    #region Interface
    
    // 定义观察者接口
    public interface IObserver
    {
        void Update(string message);
        void Unsubscribe();
    }
    
    // 定义目标接口
    public interface ISubject
    {
        void Attach(IObserver observer);
        void Detach(IObserver observer);
        void Notify(string message);
    }
    
    #endregion
  • 具体目标类 Subject

    在目标对象被销毁之前,通知所有观察者解除订阅

    c# 复制代码
    #region 定义具体目标类 : Subject
    
    // 定义具体目标(主题)类
    public class Subject : ISubject
    {
        private List _observers = new List();
    
        public void Attach(IObserver observer)
        {
            _observers.Add(observer);
        }
    
        public void Detach(IObserver observer)
        {
            _observers.Remove(observer);
        }
    
        public void Notify(string message)
        {
            foreach (var observer in _observers)
            {
                observer.Update(message);
            }
        }
    
        // 在目标对象被销毁之前,通知所有观察者解除订阅
        public void Dispose()
        {
            foreach (var observer in _observers)
            {
                observer.Unsubscribe();
            }
    
            _observers.Clear();
        }
    }
    
    
    #endregion
  • 具体观察者类 ConcreteObserver

    c# 复制代码
    #region 具体观察者类 : ConcreteObserver
    // 定义具体观察者类
    public class ConcreteObserver : IObserver
    {
        private string _name;
        private Subject _subject;
    
        public ConcreteObserver(string name, Subject subject)
        {
            _name = name;
            _subject = subject;
        }
    
        public void Update(string message)
        {
            Console.WriteLine($"{_name} received message: {message}");
        }
    
        public void Unsubscribe()
        {
            if (_subject != null)
            {
                _subject.Detach(this);
                _subject = null;
            }
        }
    }
    #endregion
  • 使用实例 Client Code

    在销毁目标对象之前,通知所有观察者解除订阅.

    c# 复制代码
    #region Client Code
    
    Subject subject = new Subject();
    
    IObserver observer1 = new ConcreteObserver("observer 1", subject);
    IObserver observer2 = new ConcreteObserver("observer 2", subject);
    
    subject.Attach(observer1);
    subject.Attach(observer2);
    
    subject.Notify("Hello Observers!");
    
    // 在销毁目标对象之前,通知所有观察者解除订阅
    subject.Dispose();
    
    // 尝试再次通知(应该不会有任何效果,因为观察者已被移除)
    subject.Notify("This should not be received.");
    
    #endregion
     

    结果

    c# 复制代码
    Observer 1 received message: Hello Observers!
    Observer 2 received message: Hello Observers!
相关推荐
xiaoshuaishuai81 小时前
C# AvaloniaUI 资源找不到报错
java·服务器·前端·windows·c#
Xin_ye100861 小时前
C# 零基础到精通教程 - 第十八章:部署与发布——让应用上线
开发语言·c#
爱讲故事的2 小时前
操作系统第一讲复习:为什么学习操作系统,以及操作系统到底在做什么?
linux·开发语言·windows·学习·ubuntu·c#
JaydenAI3 小时前
[MAF预定义的AIContextProvider-03]ChatHistoryMemoryProvider——赋予Agent从经验中学习的能力
ai·c#·agent·memory·maf
lqqjuly4 小时前
设计模式:理论、架构与 C++ 实现—SOLID原则到23 种经典模式
c++·设计模式·架构
z落落5 小时前
C# 继承:父子构造函数 + base 关键字 +五大访问修饰符(同项目+跨项目 全覆盖)
开发语言·c#
海盗12345 小时前
C#中PDF操作-QuestPDF页面设置与布局
java·pdf·c#
玩c#的小杜同学5 小时前
一周 AI 新鲜事|2026.05.25—2026.05.31
人工智能·程序人生·ai·c#·程序员创富
周杰伦fans5 小时前
C# 异常继承深度解析:从设计原则到 sealed 关键字的奥秘
java·jvm·c#
多巴胺耐受6 小时前
【WPF】炫酷的科技报警弹窗
科技·c#·wpf