观察者模式
概述
也被称为发布订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己
观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子(邮件订阅、RSS Feeds,本质上都是观察者模式。)
不同的应用场景和需求下,这个模式也有截然不同的实现方式,有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。
跨进程之间的观察者可以通过RPC(grpc框架)或者消息队列的方式进行实现
结构图
代码实现
同步实现
主题
/// <summary> /// 抽象主题 /// </summary> public abstract class Subject { private IList<Observer> observers = new List<Observer>(); //增加观察者 public void Attach(Observer observer) { observers.Add(observer); } //移除观察者 public void Detach(Observer observer) { observers.Remove(observer); } //通知 public void Notify() { foreach (Observer o in observers) { o.Update(); } } }
/// <summary> ///具体主题 /// </summary> public class ConcreteSubject : Subject { private string subjectState; //具体通知者状态 public string SubjectState { get { return subjectState; } set { subjectState = value; } } }
观察者
/// <summary> /// 抽象观察者 /// </summary> public abstract class Observer { public abstract void Update(); }
/// <summary> /// 具体观察者 /// </summary> public class ConcreteObserver : Observer { private string name; private string observerState; private ConcreteSubject subject; public ConcreteObserver(ConcreteSubject subject, string name) { this.subject = subject; this.name = name; } //更新 public override void Update() { observerState = subject.SubjectState; Console.WriteLine("观察者{0}的新状态是{1}", name, observerState); } public ConcreteSubject Subject { get { return subject; } set { subject = value; } } }
客户端
class Program { static void Main(string[] args) { ConcreteSubject s = new ConcreteSubject(); s.Attach(new ConcreteObserver(s, "X")); s.Attach(new ConcreteObserver(s, "Y")); s.Attach(new ConcreteObserver(s, "Z")); s.SubjectState = "ABC"; s.Notify(); Console.Read(); } }
运行结果
简单的非阻塞异步
主要思路就是在进行执行通知订阅信息操作时使用线程
但是若非常频繁的调用这个方法,就会频繁的创建和销毁线程,若线程的创建速度大于销毁速度就有可能出现内存溢出的问题
具体主题
/// <summary> ///具体主题 /// </summary> public class AsyncConcreteSubject : Subject { private string subjectState; //具体通知者状态 public string SubjectState { get { return subjectState; } set { subjectState = value; } } public override void Notify() { //执行耗时操作 Thread.Sleep(1000); Task.Run(() => { base.Notify(); }); Console.WriteLine("具体主题发送完成了通知"); } }
EventBus的方式
EventBus类
public class EventBus { private List<object> observers = new List<object>(); public void Attach(object obj) { observers.Add(obj); } public void Detach(object obj) { observers.Remove(obj); } public void Notify(object parameter) { foreach (var observer in observers) { Type type = observer.GetType(); MethodInfo[] methods = type.GetMethods(); foreach (var method in methods) { EventBusFunctionAttribute attribute = method.GetCustomAttribute<EventBusFunctionAttribute>(); if (attribute != null) { if (attribute.ParameterType == parameter.GetType()) { method.Invoke(observer, new object[] { parameter }); } } } } } }
特性标记类
/// <summary> /// 用于标记观察者执行的方法 /// </summary> [AttributeUsage(AttributeTargets.Method)] public class EventBusFunctionAttribute : Attribute { private Type parameterType; public Type ParameterType { get { return parameterType; } } public EventBusFunctionAttribute(Type parameterType) { this.parameterType = parameterType; } }
观察者
public class Subject1 { public string c1 { get; set; } public string c2 { get; set; } } public class Concrete1Observer { [EventBusFunction(typeof(Subject1))] public void Update(object obj) { Subject1 subject1 = obj as Subject1; Console.WriteLine("Concrete1Observer接收到数据" + subject1.c1 + "," + subject1.c2); } }
public class Subject2 { public string c3 { get; set; } public string c4 { get; set; } } public class Concrete2Observer { [EventBusFunction(typeof(Subject2))] public void Operation(object obj) { Subject2 subject2 = obj as Subject2; Console.WriteLine("Concrete2Observer接收到数据" + subject2.c3 + "," + subject2.c4); } }
客户端
static void Main(string[] args) { EventBus eventBus = new EventBus(); Concrete1Observer concrete11Observer = new Concrete1Observer(); Concrete1Observer concrete12Observer = new Concrete1Observer(); Concrete2Observer concrete2Observer = new Concrete2Observer(); eventBus.Attach(concrete11Observer); eventBus.Attach(concrete12Observer); eventBus.Attach(concrete2Observer); eventBus.Notify(new Subject1() { c1 = "1111", c2 = "2222", }); eventBus.Notify(new Subject2() { c3 = "3333", c4 = "4444", }); Console.Read(); }
运行结果
优势
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则
目标与观察者之间建立了一套触发机制。
使用场景
一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
缺陷
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人