【再谈设计模式】观察者模式~对象间依赖关系的信使

一、引言

在软件工程、软件开发的世界里,设计模式如同建筑蓝图中的经典结构,帮助开发者构建更加灵活、可维护和可扩展的软件系统。观察者模式就是其中一种极为重要的行为型设计模式,它在处理对象间的一对多关系时展现出独特的魅力。

二、定义与描述

观察者模式定义了对象之间的一种一对多依赖关系。其中有一个被观察的对象(称为主题Subject)和多个观察该对象的观察者(Observer)。主题对象负责维护一组观察者对象,并在自身状态发生改变时通知所有观察者。这种模式使得对象之间的耦合度降低,主题和观察者可以独立地进行扩展和修改。

三、抽象背景

在很多实际的软件场景中,存在着对象状态变化需要通知其他对象的需求。例如,在一个新闻发布系统中,当有新的新闻发布(新闻对象状态改变)时,订阅了该新闻频道的用户(观察者)需要及时得到通知;或者在一个游戏开发中,当游戏角色的某些属性(如生命值、位置等)发生变化时,与之相关的UI界面元素(观察者)需要更新显示。

四、适用场景与现实问题解决

  • 事件驱动系统
    • 在图形用户界面(GUI)开发中,用户的操作(如点击按钮、输入文本等)会触发事件。这些事件可以看作是主题的状态变化,而处理这些事件的各个组件(如菜单更新、数据显示等)就是观察者。通过观察者模式,可以很方便地实现事件的分发和处理,使不同的组件能够独立地响应事件。
  • 股票市场监测
    • 当股票价格发生变化(主题状态改变)时,多个投资者(观察者)需要得到通知以便做出相应的决策。使用观察者模式可以高效地实现这种通知机制,而不需要在股票价格变化的代码中硬编码每个投资者的通知逻辑。

五、观察者模式的现实生活的例子

  • 社交媒体平台
    • 当一个用户(主题)发布了一条新的动态时,他的好友(观察者)会收到通知。这里,用户是被观察的对象,好友们是观察者。社交平台负责维护好友关系(即主题中的观察者列表),并在用户发布新动态时通知所有好友。
  • 气象站与订阅者
    • 气象站(主题)负责收集气象数据并检测天气状态的变化。当天气状态发生变化(如温度、湿度、气压等数据变化)时,气象站会通知所有订阅了气象信息的用户(观察者),如农民、飞行员、户外运动爱好者等。

六、初衷与问题解决

  • 初衷
    • 观察者模式的初衷是为了实现对象之间的松耦合关系。在没有这种模式的情况下,如果一个对象的状态变化需要通知其他对象,可能会导致高度耦合的代码,即变化的对象需要直接调用其他对象的方法来通知它们。这使得代码难以维护和扩展,因为任何一个相关对象的改变都可能影响到其他对象。
  • 问题解决
    • 通过观察者模式,主题和观察者之间通过抽象的接口进行交互。主题只需要维护一个观察者列表,并在状态变化时调用观察者的抽象通知方法。这样,主题不需要知道具体的观察者类型,观察者也不需要知道主题的具体实现细节。当有新的观察者或主题需要加入系统时,只需要实现相应的接口即可,不会影响到其他部分的代码。

七、代码示例

Java示例

java 复制代码
import java.util.ArrayList;
import java.util.List;

// 观察者接口
interface Observer {
    void update(String message);
}

// 主题类
class Subject {
    private List<Observer> observers = new ArrayList<>();
    private String state;

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void setState(String state) {
        this.state = state;
        notifyAllObservers();
    }

    private void notifyAllObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }
}

// 具体观察者类
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);
    }
}

public class Main {
    public static void main(String[] args) {
        Subject subject = new Subject();

        Observer observer1 = new ConcreteObserver("Observer 1");
        Observer observer2 = new ConcreteObserver("Observer 2");

        subject.attach(observer1);
        subject.attach(observer2);

        subject.setState("New state!");
    }
}

类图:

Subject类与Observer接口之间是一对多的关系(Subject可以有多个ObserverConcreteObserver类实现了Observer接口。

C++示例

cpp 复制代码
#include <iostream>
#include <vector>

// 观察者抽象类
class Observer {
public:
    virtual void update(std::string message) = 0;
};

// 主题类
class Subject {
private:
    std::vector<Observer*> observers;
    std::string state;
public:
    void attach(Observer* observer) {
        observers.push_back(observer);
    }

    void detach(Observer* observer) {
        for (auto it = observers.begin(); it!= observers.end(); ++it) {
            if (*it == observer) {
                observers.erase(it);
                break;
            }
        }
    }

    void setState(std::string state) {
        this.state = state;
        notifyAllObservers();
    }

    void notifyAllObservers() {
        for (auto observer : observers) {
            observer->update(state);
        }
    }
};

// 具体观察者类
class ConcreteObserver : public Observer {
private:
    std::string name;
public:
    ConcreteObserver(std::string name) : name(name) {}

    void update(std::string message) override {
        std::cout << name << " received message: " << message << std::endl;
    }
};

int main() {
    Subject subject;

    Observer* observer1 = new ConcreteObserver("Observer 1");
    Observer* observer2 = new ConcreteObserver("Observer 2");

    subject.attach(observer1);
    subject.attach(observer2);

    subject.setState("New state!");

    return 0;
}

Python示例

python 复制代码
# 观察者抽象类
class Observer:
    def update(self, message):
        pass

# 主题类
class Subject:
    def __init__(self):
        self.observers = []
        self.state = None

    def attach(self, observer):
        self.observers.append(observer)

    def detach(self, observer):
        self.observers.remove(observer)

    def setState(self, state):
        self.state = state
        self.notifyAllObservers()

    def notifyAllObservers(self):
        for observer in self.observers:
            observer.update(self.state)


# 具体观察者类
class ConcreteObserver(Observer):
    def __init__(self, name):
        self.name = name

    def update(self, message):
        print(f"{self.name} received message: {message}")


if __name__ == "__main__":
    subject = Subject()

    observer1 = ConcreteObserver("Observer 1")
    observer2 = ConcreteObserver("Observer 2")

    subject.attach(observer1)
    subject.attach(observer2)

    subject.setState("New state!")

Go示例

Go 复制代码
package main

import (
    "fmt"
)

// 观察者接口
type Observer interface {
    update(message string)
}

// 主题结构体
type Subject struct {
    observers []Observer
    state     string
}

// 附加观察者
func (s *Subject) attach(observer Observer) {
    s.observers = append(s.observers, observer)
}

// 分离观察者
func (s *Subject) detach(observer Observer) {
    for i, obs := range s.observers {
        if obs == observer {
            s.observers = append(s.observers[:i], s.observers[i+1:]...)
            break
        }
    }
}

// 设置状态并通知观察者
func (s *Subject) setState(state string) {
    s.state = state
    s.notifyAllObservers()
}

// 通知所有观察者
func (s *Subject) notifyAllObservers() {
    for _, observer := range s.observers {
        observer.update(s.state)
    }
}

// 具体观察者结构体
type ConcreteObserver struct {
    name string
}

// 具体观察者实现更新方法
func (co *ConcreteObserver) update(message string) {
    fmt.Printf("%s received message: %s\n", co.name, message)
}

func main() {
    subject := Subject{}

    observer1 := ConcreteObserver{name: "Observer 1"}
    observer2 := ConcreteObserver{name: "Observer 2"}

    subject.attach(&observer1)
    subject.attach(&observer2)

    subject.setState("New state!")
}

八、观察者模式的优缺点

  • 优点
    • 松耦合:主题和观察者之间是松耦合的关系。主题不需要知道观察者的具体实现,只需要调用观察者的抽象接口。这使得在系统中添加或删除观察者非常容易,不会影响到主题的代码。
    • 可扩展性:可以很容易地增加新的观察者,只需要实现观察者接口即可。同样,主题也可以在不影响观察者的情况下进行扩展。
    • 支持广播通信:一个主题可以通知多个观察者,实现了一对多的消息传递,适合于需要将信息广播给多个对象的场景。
  • 缺点
    • 可能存在通知顺序问题:如果有多个观察者,当主题通知观察者时,可能会存在通知顺序不确定的问题。这在某些对顺序有严格要求的场景下可能会导致问题。
    • 性能开销:如果观察者数量较多,当主题状态发生变化时,通知所有观察者可能会带来一定的性能开销。特别是在观察者的更新操作比较复杂时,这种开销会更加明显。

九、观察者模式的升级版

  • 事件委托模型
    • 在传统的观察者模式中,主题直接通知所有的观察者。而在事件委托模型中,引入了事件源、事件和事件处理程序的概念。事件源(类似于主题)产生事件,事件包含了关于状态变化的信息,事件处理程序(类似于观察者)负责处理事件。事件委托模型更加灵活,可以根据事件的类型、优先级等因素来决定如何处理事件,而不是简单地通知所有观察者。
  • 反应式编程中的观察者模式扩展
    • 在反应式编程(如RxJava、ReactiveX等)中,观察者模式得到了进一步的扩展。反应式编程关注的是数据的流动和异步处理。在这种模式下,观察者可以对数据的变化做出反应,并且可以组合、转换和过滤数据。例如,在RxJava中,可以使用操作符来对数据流进行操作,然后再将处理后的结果通知给观察者。这使得观察者模式在处理异步和复杂的数据处理场景时更加高效和灵活。
相关推荐
我不会编程5552 小时前
Python Cookbook-5.1 对字典排序
开发语言·数据结构·python
李少兄2 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝2 小时前
【设计模式】原型模式
java·设计模式·原型模式
无名之逆2 小时前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
似水এ᭄往昔2 小时前
【C语言】文件操作
c语言·开发语言
啊喜拔牙2 小时前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
owde2 小时前
顺序容器 -list双向链表
数据结构·c++·链表·list
xixixin_2 小时前
为什么 js 对象中引用本地图片需要写 require 或 import
开发语言·前端·javascript
第404块砖头2 小时前
分享宝藏之List转Markdown
数据结构·list
W_chuanqi3 小时前
安装 Microsoft Visual C++ Build Tools
开发语言·c++·microsoft