文章摘要
观察者模式是一种松耦合设计模式,通过订阅/通知机制实现对象间通信。典型应用如报社-读者场景:报社(被观察者)出版新报纸时自动通知所有订阅者(观察者),读者可随时订阅或退订。C#提供了多种实现方式:1)基础接口实现(ISubject/IObserver);2)委托事件机制;3)标准EventHandler方式。该模式适用于需要动态通知的场景,如GUI事件、消息推送等,核心优势是降低对象间的直接依赖,提高系统可扩展性。代码示例展示了完整的订阅、通知和取消订阅流程,以及如何传递复杂事件参数。
一、观察者模式(Observer)生动形象解释
1. 生活中的例子
报纸订阅:
你订阅了一份报纸,每当有新报纸出版时,报社就会把报纸送到你家门口。你不需要天天打电话问“今天有新报纸吗?”,只要有新内容,报社会主动通知你。
如果你不想看了,取消订阅即可,报社以后也不会再送报纸给你。
对应到代码:
- 报社:被观察者(Subject)
- 你:观察者(Observer)
- 订阅/退订:注册/移除观察者
- 出版新报纸:状态变化,通知所有观察者
2. 适用场景
- 一个对象状态变化,需要通知其他对象,并且不希望这些对象紧密耦合。
- 典型如:事件系统、GUI事件、消息推送、数据绑定等。
二、C#代码实现
1. 定义观察者接口
public interface IObserver
{
void Update(string message);
}
2. 定义被观察者接口
public interface ISubject
{
void Attach(IObserver observer); // 添加观察者
void Detach(IObserver observer); // 移除观察者
void Notify(string message); // 通知所有观察者
}
3. 实现具体的被观察者(报社)
using System.Collections.Generic;
public class Newspaper : ISubject
{
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 Notify(string message)
{
foreach (var observer in observers)
{
observer.Update(message);
}
}
// 新报纸出版
public void Publish(string content)
{
Console.WriteLine("报社:新报纸出版啦!");
Notify(content);
}
}
4. 实现具体的观察者(订阅者)
public class Reader : IObserver
{
public string Name { get; set; }
public Reader(string name)
{
Name = name;
}
public void Update(string message)
{
Console.WriteLine($"{Name} 收到报纸:{message}");
}
}
5. 使用示例
class Program
{
static void Main(string[] args)
{
Newspaper newspaper = new Newspaper();
Reader alice = new Reader("Alice");
Reader bob = new Reader("Bob");
// 订阅
newspaper.Attach(alice);
newspaper.Attach(bob);
// 发布新报纸
newspaper.Publish("2024年6月新闻:观察者模式真有趣!");
// Bob 退订
newspaper.Detach(bob);
// 再次发布
newspaper.Publish("2024年7月新闻:C#代码实现观察者模式!");
}
}
6. 输出结果
报社:新报纸出版啦!
Alice 收到报纸:2024年6月新闻:观察者模式真有趣!
Bob 收到报纸:2024年6月新闻:观察者模式真有趣!
报社:新报纸出版啦!
Alice 收到报纸:2024年7月新闻:C#代码实现观察者模式!
总结口诀
观察者模式像订报,
报社有新就通知,
订阅退订都灵活,
低耦合、扩展易!
下面我将分别介绍更复杂的事件参数、委托实现和**.NET内置事件实现**,让你对观察者模式在C#中的实际应用有更深入的理解。
一、复杂事件参数
有时候,通知不仅仅是一个字符串,可能需要传递更多信息。可以自定义事件参数类(通常继承自EventArgs
)。
public class NewspaperEventArgs : EventArgs
{
public string Title { get; set; }
public string Content { get; set; }
public DateTime PublishDate { get; set; }
public NewspaperEventArgs(string title, string content, DateTime date)
{
Title = title;
Content = content;
PublishDate = date;
}
}
二、委托实现
C#中,委托(Delegate)是实现观察者模式的常用方式。你可以定义一个委托类型,然后让观察者注册回调。
// 定义委托
public delegate void NewspaperPublishedHandler(object sender, NewspaperEventArgs e);
public class Newspaper
{
// 声明事件
public event NewspaperPublishedHandler NewspaperPublished;
public void Publish(string title, string content)
{
Console.WriteLine("报社:新报纸出版啦!");
// 触发事件
NewspaperPublished?.Invoke(this, new NewspaperEventArgs(title, content, DateTime.Now));
}
}
观察者(订阅者)只需注册事件:
public class Reader
{
public string Name { get; set; }
public Reader(string name) { Name = name; }
public void OnNewspaperPublished(object sender, NewspaperEventArgs e)
{
Console.WriteLine($"{Name} 收到报纸:《{e.Title}》, 内容:{e.Content}, 日期:{e.PublishDate}");
}
}
使用示例:
class Program
{
static void Main(string[] args)
{
Newspaper newspaper = new Newspaper();
Reader alice = new Reader("Alice");
Reader bob = new Reader("Bob");
// 订阅事件
newspaper.NewspaperPublished += alice.OnNewspaperPublished;
newspaper.NewspaperPublished += bob.OnNewspaperPublished;
newspaper.Publish("观察者模式", "C#事件机制详解");
// Bob 退订
newspaper.NewspaperPublished -= bob.OnNewspaperPublished;
newspaper.Publish("委托与事件", "深入理解C#委托与事件");
}
}
三、.NET内置事件实现(推荐写法)
.NET推荐事件声明方式是使用EventHandler<TEventArgs>
,这样更规范。
public class Newspaper
{
// 使用泛型EventHandler
public event EventHandler<NewspaperEventArgs> NewspaperPublished;
public void Publish(string title, string content)
{
Console.WriteLine("报社:新报纸出版啦!");
NewspaperPublished?.Invoke(this, new NewspaperEventArgs(title, content, DateTime.Now));
}
}
订阅和取消订阅方式与上面一致:
public class Reader
{
public string Name { get; set; }
public Reader(string name) { Name = name; }
public void OnNewspaperPublished(object sender, NewspaperEventArgs e)
{
Console.WriteLine($"{Name} 收到报纸:《{e.Title}》, 内容:{e.Content}, 日期:{e.PublishDate}");
}
}
四、总结
- 自定义事件参数:让通知内容更丰富。
- 委托实现:灵活注册/注销回调,解耦合。
- .NET内置事件:推荐用
event EventHandler<TEventArgs>
,更规范,易于维护。
口诀
委托事件来通知,
参数丰富用EventArgs;
订阅退订都灵活,
.NET推荐EventHandler!