【软件设计】常用设计模式--观察者模式
软件设计模式(四)
- 观察者模式
- 一、观察者模式(Observer Pattern)
- 1. 概念
- 2. 模式结构
- 3. UML 类图
- 4. 实现方式
- C# 示例
- 步骤1:定义观察者接口
- 步骤2:定义主题接口
- 步骤3:实现具体主题
- 步骤4:实现具体观察者
- 步骤5:使用观察者模式
- Java 示例
- 步骤1:定义观察者接口
- 步骤2:定义主题接口
- 步骤3:实现具体主题
- 步骤4:实现具体观察者
- 步骤5:使用观察者模式
- 5. 优点
- 6. 缺点
- 7. 应用场景
- 二、观察者模式的变体与深入应用
- 1. 观察者模式的变体
- 变体1: 推与拉模式(Push vs. Pull Model)
- 变体2: 同步与异步通知
- 变体3: 链式观察者模式
- 变体4: 优先级观察者
- 2. 实际应用场景
- 场景1: GUI 事件系统中的事件分发
- 场景2: 数据流处理与实时监控
- 场景3: 发布-订阅系统
- 场景4: MVC(Model-View-Controller)模式
- 场景5: 微服务架构中的事件驱动
- 3. 观察者模式的实际挑战
- 问题1: 过度依赖
- 问题2: 循环依赖
- 问题3: 内存泄漏
- 总结
观察者模式
一、观察者模式(Observer Pattern)
1. 概念
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。观察者模式常用于实现分布式事件处理系统,特别是在GUI框架和事件驱动的系统中。
通过观察者模式,多个对象可以观察同一个主题(Subject),当主题状态变化时,所有观察者(Observer)都会收到通知并做出相应的响应。
2. 模式结构
观察者模式包含以下几个关键角色:
-
主题(Subject):持有观察者的引用,并负责管理观察者(添加、移除、通知等)。
-
观察者(Observer):定义一个更新接口,当主题状态变化时更新自己。
-
具体主题(Concrete Subject):实现主题的通知逻辑,维护内部状态并在状态变化时通知观察者。
-
具体观察者(Concrete Observer):实现观察者的更新接口,根据主题通知的变化做出响应。
3. UML 类图
4. 实现方式
C# 示例
步骤1:定义观察者接口
public interface IObserver
{
void Update(string message);
}
步骤2:定义主题接口
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
步骤3:实现具体主题
public class ConcreteSubject : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
private string _subjectState;
public string State
{
get { return _subjectState; }
set
{
_subjectState = 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(_subjectState);
}
}
}
步骤4:实现具体观察者
public class ConcreteObserver : IObserver
{
private string _name;
public ConcreteObserver(string name)
{
_name = name;
}
public void Update(string message)
{
Console.WriteLine($"{_name} received message: {message}");
}
}
步骤5:使用观察者模式
class Program
{
static void Main(string[] args)
{
// 创建主题
ConcreteSubject subject = new ConcreteSubject();
// 创建观察者
IObserver observer1 = new ConcreteObserver("Observer 1");
IObserver observer2 = new ConcreteObserver("Observer 2");
// 注册观察者
subject.Attach(observer1);
subject.Attach(observer2);
// 改变主题状态并通知观察者
subject.State = "State changed!";
// Output:
// Observer 1 received message: State changed!
// Observer 2 received message: State changed!
}
}
Java 示例
步骤1:定义观察者接口
public interface Observer {
void update(String message);
}
步骤2:定义主题接口
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
步骤3:实现具体主题
import java.util.ArrayList;
import java.util.List;
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String state;
public void setState(String state) {
this.state = state;
notifyObservers();
}
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(state);
}
}
}
步骤4:实现具体观察者
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
步骤5:使用观察者模式
public class Main {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver("Observer 1");
Observer observer2 = new ConcreteObserver("Observer 2");
subject.attach(observer1);
subject.attach(observer2);
subject.setState("New State");
// Output:
// Observer 1 received message: New State
// Observer 2 received message: New State
}
}
5. 优点
- 松耦合: 观察者与主题之间的耦合度较低,主题不需要了解观察者的具体实现。
- 动态观察者管理: 可以随时添加或移除观察者,不影响系统的其他部分。
- 事件通知机制: 广泛应用于事件驱动的系统中,实现异步通知机制。
6. 缺点
- 通知顺序问题: 多个观察者的通知顺序可能无法确定,导致结果的不可预测性。
- 性能开销: 如果观察者数量过多,通知操作可能带来性能开销。
- 内存泄漏风险: 如果没有正确地解除观察者注册,可能会造成内存泄漏。
7. 应用场景
- 场景1: GUI事件系统
观察者模式广泛用于GUI应用程序中,按钮的点击、文本框的输入等事件都可以通过观察者模式通知相关对象。 - 场景2: 订阅-发布系统
在消息系统中,发布者可以是主题,订阅者是观察者。当发布者有新消息时,订阅者会收到通知。 - 场景3: 数据绑定
在现代前端框架(如React、Vue等)中,观察者模式用于实现数据的双向绑定,当数据变化时自动更新视图。
二、观察者模式的变体与深入应用
1. 观察者模式的变体
观察者模式虽然简单,但可以通过不同的方式进行扩展和变体以适应更复杂的场景。以下是几种常见的变体:
变体1: 推与拉模式(Push vs. Pull Model)
在经典的观察者模式中,通常采用推(Push)模式,即主题在状态改变时主动将更新数据推送给所有观察者。而在拉(Pull)模式中,主题只通知观察者有变化,但具体数据由观察者自行从主题中拉取。
- 推模式:主题向观察者提供足够的更新数据,观察者直接获取并处理。适合更新数据明确的场景。
- 拉模式:主题只提供最小的信息,观察者决定是否拉取完整数据,通常用于复杂的更新场景,以减少不必要的传输。
// 拉模式下,Observer在收到通知后,主动获取数据
public void Update(ConcreteSubject subject)
{
var state = subject.GetState();
Console.WriteLine($"Observer fetched new state: {state}");
}
变体2: 同步与异步通知
经典的观察者模式通常是同步通知,即主题在通知观察者时,所有观察者都同步更新。但在某些高负载或分布式系统中,可以采用异步通知,即主题在状态改变时,异步通知观察者,允许观察者在不同的时间点处理更新。
- 同步通知:适合简单或小规模系统,能确保观察者在通知后立即获取最新状态。
- 异步通知:适合需要并发处理或系统延迟的场景,如消息队列、分布式系统。
变体3: 链式观察者模式
在链式观察者模式中,观察者之间形成链式依赖,一个观察者更新后会通知下一个观察者,依次传递下去。适合于需要逐步更新的场景,如责任链模式与观察者模式结合使用。
public class ChainedObserver : IObserver
{
private IObserver _nextObserver;
public ChainedObserver(IObserver nextObserver)
{
_nextObserver = nextObserver;
}
public void Update(string message)
{
Console.WriteLine($"Processing in current observer: {message}");
_nextObserver?.Update(message);
}
}
变体4: 优先级观察者
在某些系统中,可能需要不同观察者根据优先级来接收通知。优先级观察者模式允许为每个观察者分配一个优先级,主题按照优先级的顺序通知观察者。这样可以确保关键观察者优先响应,减少延迟。
// 基于优先级的通知系统
public class PriorityObserver : IObserver, IComparable<PriorityObserver>
{
public int Priority { get; }
public PriorityObserver(int priority)
{
Priority = priority;
}
public void Update(string message)
{
Console.WriteLine($"Priority {Priority} observer received: {message}");
}
public int CompareTo(PriorityObserver other)
{
return Priority.CompareTo(other.Priority);
}
}
2. 实际应用场景
场景1: GUI 事件系统中的事件分发
现代GUI应用程序(如WPF、Swing、WinForms等)经常使用观察者模式处理用户界面上的事件。例如,当用户点击按钮时,按钮是主题,它的点击事件会触发多个观察者(如回调函数、事件处理程序),以执行相关逻辑。
- 推模式:可以将用户操作的详细信息(如点击坐标、鼠标状态)推送给观察者。
- 拉模式:观察者只收到“按钮被点击”这一基本通知,然后自行获取相关的事件数据。
场景2: 数据流处理与实时监控
在金融市场、物联网、实时监控等场景中,观察者模式可以用于数据流的处理与监控。例如,股票价格实时变动时,所有观察者(投资分析工具、交易平台等)都会被通知。
- 异步通知:由于数据量大,可以通过消息队列实现异步更新,观察者可以根据需要处理数据流。
- 优先级通知:关键监控设备或算法可能需要优先处理,以确保及时响应市场变化。
场景3: 发布-订阅系统
观察者模式在发布-订阅系统中广泛使用。发布者(主题)发布消息时,所有订阅者(观察者)会接收消息并更新。这种系统应用于多种场景,如新闻订阅、RSS订阅、电子邮件通知等。
- 拉模式:订阅者可以根据消息头信息判断是否需要拉取完整内容,适合减少带宽占用的场景。
- 异步处理:特别适合分布式系统,订阅者可以异步接收消息并处理。
场景4: MVC(Model-View-Controller)模式
MVC是软件开发中的一种常见架构模式,观察者模式在其中扮演了重要角色。模型(Model)作为主题,持有应用程序的数据,当数据改变时通知视图(View),视图可以通过观察者模式动态更新界面。控制器(Controller)作为观察者与模型交互,推动状态的变化。
// MVC中的简单应用
public class Model : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
private string _data;
public string Data
{
get => _data;
set
{
_data = 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(_data);
}
}
public class View : IObserver
{
public void Update(string message)
{
Console.WriteLine($"View updated with new data: {message}");
}
}
场景5: 微服务架构中的事件驱动
在微服务架构中,不同的服务之间通过事件进行通信。每个服务可以作为一个观察者,当某个服务发布事件时,所有依赖的服务都会收到通知并做出相应处理。这种架构通过事件驱动模式实现高解耦和灵活扩展。
- 异步通知:通过消息队列(如RabbitMQ、Kafka)实现异步事件驱动,确保服务的独立性与高效通信。
- 优先级处理:某些关键服务可以优先获取事件,并立即作出响应。
3. 观察者模式的实际挑战
问题1: 过度依赖
观察者模式虽然在解耦上提供了灵活性,但在某些复杂场景下,系统可能会过度依赖观察者,导致难以追踪更新的来源和原因。
问题2: 循环依赖
如果多个观察者之间相互依赖,可能会引发循环通知的问题,导致系统陷入死循环或过多的无效更新。
问题3: 内存泄漏
如果主题没有正确地管理观察者(如没有移除已经不需要的观察者),可能会导致内存泄漏,特别是在长生命周期的系统中。
总结
观察者模式的变体为不同的应用场景提供了灵活性,从推拉模型到优先级处理和异步通知,它适用于从简单的GUI事件系统到复杂的分布式微服务架构。在选择具体变体时,需要权衡性能、响应速度、解耦程度等多方面因素,以便为系统设计提供最佳的解决方案。