设计模式 --- 观察者模式
- 什么是观察者模式
- 观察者模式典型应用 --- C#中的事件
- 使用观察者模式实现事件处理机制
什么是观察者模式
- 观察者模式(Observer Pattern)是一种行为型设计模式,用于在对象之间建立一对多的依赖关系。当一个对象(称为“主题”Subject)的状态发生改变时,所有依赖它的对象(称为“观察者”Observer)会自动收到通知并更新。这种模式的核心是解耦主题和观察者,让它们可以独立变化。
- 核心思想
主题(Subject)
:维护一个观察者列表,提供注册、注销和通知机制。
观察者(Observer)
:定义统一的更新接口,当主题状态变化时被调用。
松耦合:
主题不需要知道观察者的具体实现,只需通过接口通知
观察者模式典型应用 — C#中的事件
事件是什么?
- 事件(Event)是 C# 中的一种语言机制,允许一个对象(发布者)在特定动作发生时,通知其他对象(订阅者)执行响应逻辑。
它本质上是基于委托(Delegate)的封装,提供了一种安全、可控的观察者模型。
- 核心特点:
- 主题定义事件:声明事件并决定何时触发。
- 观察者注册方法:将方法绑定到事件,事件触发时自动调用。
- 解耦设计:主题和观察者无需直接依赖彼此的具体实现。
事件的原理
- 底层机制:委托 + 观察者模式
-
委托(Delegate):
事件本质是一个多播委托(MulticastDelegate
),可以存储多个方法引用。当事件触发时,所有绑定的方法会被依次调用。 -
观察者模式(Observer Pattern):
- 发布者(Subject):维护一个订阅者列表(通过委托链实现),提供订阅(
+=
)和取消订阅(-=
)接口。 - 订阅者(Observer):向发布者注册事件处理方法,事件触发时自动执行。
- 发布者(Subject):维护一个订阅者列表(通过委托链实现),提供订阅(
完整示例:温度监控系统
- 场景描述
- 发布者:
TemperatureMonitor
类,监控温度变化。 - 订阅者:
AlertSystem
:温度过高时触发警报。Logger
:记录温度事件到日志。
- 关键流程:
- 定义事件:使用
event
关键字声明一个委托类型的事件。- 订阅事件:通过
+=
将方法添加到事件的委托链。- 触发事件:在特定条件下调用事件,执行所有订阅的方法。
代码实现
using System;
// 1. 定义事件参数(传递温度数据)
public class TemperatureEventArgs : EventArgs
{
public double Temperature { get; }
public TemperatureEventArgs(double temperature)
{
Temperature = temperature;
}
}
// 2. 发布者(Subject)
public class TemperatureMonitor
{
// 声明事件(基于泛型 EventHandler<T> 委托)
// EventHandler<T> 是 .NET 内置的泛型委托,定义形式为:
// delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
public event EventHandler<TemperatureEventArgs> TemperatureExceeded;
private double _currentTemperature;
public double Threshold { get; set; } = 30.0;
public void UpdateTemperature(double newTemperature)
{
_currentTemperature = newTemperature;
Console.WriteLine($"当前温度: {_currentTemperature}°C");
// 触发条件:温度超过阈值
if (_currentTemperature > Threshold)
{
OnTemperatureExceeded(new TemperatureEventArgs(_currentTemperature));
}
}
// 触发事件的方法(protected virtual 便于派生类扩展)
protected virtual void OnTemperatureExceeded(TemperatureEventArgs e)
{
// 线程安全:临时保存事件引用,避免多线程下订阅者被移除导致的空引用
var handler = TemperatureExceeded;
handler?.Invoke(this, e); // 等同于 if (handler != null) handler(this, e);
}
}
// 3. 订阅者(Observer)
public class AlertSystem
{
public void HandleHighTemperature(object sender, TemperatureEventArgs e)
{
Console.WriteLine($"警报!温度过高: {e.Temperature}°C");
}
}
public class Logger
{
public void LogTemperatureEvent(object sender, TemperatureEventArgs e)
{
Console.WriteLine($"[日志] 温度事件: {e.Temperature}°C");
}
}
// 4. 使用示例
class Program
{
static void Main()
{
var monitor = new TemperatureMonitor();
var alert = new AlertSystem();
var logger = new Logger();
// 订阅事件
monitor.TemperatureExceeded += alert.HandleHighTemperature;
monitor.TemperatureExceeded += logger.LogTemperatureEvent;
// 模拟温度变化
monitor.UpdateTemperature(25); // 不触发
monitor.UpdateTemperature(32); // 触发事件
monitor.UpdateTemperature(28); // 不触发
monitor.UpdateTemperature(35); // 再次触发
// 取消订阅
monitor.TemperatureExceeded -= logger.LogTemperatureEvent;
monitor.UpdateTemperature(40); // 仅触发警报
}
}
输出结果
当前温度: 25°C
当前温度: 32°C
警报!温度过高: 32°C
[日志] 温度事件: 32°C
当前温度: 28°C
当前温度: 35°C
警报!温度过高: 35°C
[日志] 温度事件: 35°C
当前温度: 40°C
警报!温度过高: 40°C
- 观察者模式:
TemperatureMonitor
是 Subject,维护订阅者列表(通过TemperatureExceeded
事件的委托链)。- AlertSystem
和
Logger` 是 Observers,通过订阅事件实现解耦。
总结
- C# 中的事件是基于委托的观察者模式实现,通过
event
关键字提供安全的订阅机制。它广泛用于:
- GUI 编程(如按钮点击)
- 异步通知等场景,是解耦代码的关键工具
使用观察者模式实现事件处理机制
以下是一个使用 Java 手动实现事件处理机制的完整示例,基于观察者模式和自定义接口,支持线程安全的订阅、取消订阅和事件触发功能。
事件对象(Event
)
public class Event {
private String message;
private long timestamp;
public Event(String message) {
this.message = message;
this.timestamp = System.currentTimeMillis();
}
public String getMessage() {
return message;
}
public long getTimestamp() {
return timestamp;
}
}
事件监听器接口(EventListener
)
@FunctionalInterface
public interface EventListener {
void onEvent(Event event);
}
事件发布者(EventPublisher
)
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
public class EventPublisher {
// 使用线程安全的集合存储监听器
private final List<EventListener> listeners = new CopyOnWriteArrayList<>();
// 订阅事件
public void subscribe(EventListener listener) {
listeners.add(listener);
System.out.println("订阅成功: " + listener);
}
// 取消订阅
public void unsubscribe(EventListener listener) {
listeners.remove(listener);
System.out.println("取消订阅: " + listener);
}
// 触发事件
public void publishEvent(String message) {
Event event = new Event(message);
System.out.println("触发事件: " + event.getMessage());
for (EventListener listener : listeners) {
try {
listener.onEvent(event);
} catch (Exception e) {
System.err.println("监听器处理异常: " + e.getMessage());
}
}
}
}
定义具体监听器
public class Main {
public static void main(String[] args) {
EventPublisher publisher = new EventPublisher();
// 监听器1:Lambda表达式实现
EventListener listener1 = event ->
System.out.printf("[Listener1] 收到事件: %s (时间: %d)%n",
event.getMessage(), event.getTimestamp());
// 监听器2:匿名类实现
EventListener listener2 = new EventListener() {
@Override
public void onEvent(Event event) {
System.out.printf("[Listener2] 处理事件: %s%n", event.getMessage());
}
};
// 订阅
publisher.subscribe(listener1);
publisher.subscribe(listener2);
// 触发事件
publisher.publishEvent("Hello World!");
// 取消订阅listener2后再次触发
publisher.unsubscribe(listener2);
publisher.publishEvent("Another Message");
}
}
输出结果
订阅成功: Main$$Lambda$1/0x0000000800b8a440@6d311334
订阅成功: Main$1@75bd9247
触发事件: Hello World!
[Listener1] 收到事件: Hello World! (时间: 1717500000000)
[Listener2] 处理事件: Hello World!
取消订阅: Main$1@75bd9247
触发事件: Another Message
[Listener1] 收到事件: Another Message (时间: 1717500001000)
关键设计解析
- 3.1 线程安全
CopyOnWriteArrayList
:
使用线程安全的集合存储监听器,确保在遍历过程中(如触发事件时)修改监听器列表不会导致ConcurrentModificationException
。- 无锁设计:
添加/删除监听器时自动处理并发,无需显式加锁。
- 观察者模式
- Subject(主题):
EventPublisher
维护监听器列表并提供订阅/取消订阅接口。- Observer(观察者):
EventListener
接口的实现类,定义事件响应逻辑。
如果需要,可改为异步事件处理
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AsyncEventPublisher extends EventPublisher {
private final ExecutorService executor = Executors.newCachedThreadPool();
@Override
public void publishEvent(String message) {
Event event = new Event(message);
System.out.println("触发事件: " + event.getMessage());
for (EventListener listener : listeners) {
executor.submit(() -> {
try {
listener.onEvent(event);
} catch (Exception e) {
System.err.println("监听器处理异常: " + e.getMessage());
}
});
}
}
}