23. 观察者模式
原文地址: 观察者模式 更多内容请关注:智想天开
1. 观察者模式简介
观察者模式(Observer Pattern)是一种行为型设计模式,用于建立对象之间的一种一对多的依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。该模式通过 观察者(Observer) 和 被观察者(Subject) 之间的交互,实现了对象间的松散耦合。
关键点:
-
一对多关系:一个被观察者可以有多个观察者。
-
松散耦合:被观察者不需要知道观察者的具体信息,反之亦然。
-
自动通知:被观察者状态变化时,自动通知所有观察者。
2. 观察者模式的意图
观察者模式的主要目的是:
-
建立一对多的依赖关系:当一个对象(被观察者)的状态发生变化时,所有依赖于它的对象(观察者)都会被自动通知和更新。
-
实现松散耦合:被观察者不需要知道具体的观察者,实现了对象之间的低耦合。
-
支持动态的观察者管理:可以在运行时动态地增加或移除观察者,增强系统的灵活性和可扩展性。
-
简化对象间的通信:通过中介机制(被观察者),简化了对象间的直接通信。
3. 观察者模式的结构
3.1. 结构组成
观察者模式主要由以下四个角色组成:
-
Subject(被观察者):知道它的观察者,提供添加和删除观察者的方法,并在自身状态发生变化时通知观察者。
-
ConcreteSubject(具体被观察者):实现了Subject接口,维护了具体的状态,通知所有观察者其状态变化。
-
Observer(观察者):定义一个更新接口,用于接收被观察者的通知。
-
ConcreteObserver(具体观察者):实现了Observer接口,维护一个指向被观察者的引用,并在被观察者状态变化时更新自身状态。
角色关系:
-
Subject 持有一系列 Observer 的引用。
-
ConcreteSubject 在自身状态变化时,调用 Observer 的更新方法。
-
ConcreteObserver 通过 Subject 接收通知并更新自身状态。
3.2. UML类图
以下是观察者模式的简化UML类图:
+-----------------------------+ +--------------------+ | Subject |<>--------| Observer | +-----------------------------+ +--------------------+ | + attach(o: Observer): void | | | | + detach(o: Observer): void | | | | + notify(): void | | | +-----------------------------+ | + update(): void |+--------------------+^|+--------------------+| ConcreteObserver |+--------------------+| - state: String || - subject: Subject |+--------------------+| + update(): void |+--------------------++-----------------------------+ | ConcreteSubject | +-----------------------------+ | - state: String | | - observers: List<Observer> | +-----------------------------+ | + attach(o: Observer): void | | + detach(o: Observer): void | | + notify(): void | | + getState(): String | | + setState(s: String): void | +-----------------------------+
说明:
-
Subject 接口定义了观察者管理的方法和通知方法。
-
ConcreteSubject 实现了 Subject 接口,维护了状态和观察者列表,并在状态变化时通知所有观察者。
-
Observer 接口定义了
update()
方法,供被观察者调用以通知状态变化。 -
ConcreteObserver 实现了 Observer 接口,维护了对 Subject 的引用,并在
update()
方法中获取被观察者的状态以更新自身。
4. 观察者模式的实现
观察者模式的实现需要确保被观察者与观察者之间的松散耦合。以下示例将展示如何在Java和Python中实现观察者模式。以一个简单的股票价格发布系统为例,实现被观察者(股票)和观察者(投资者)之间的交互。
4.1. Java 实现示例
示例说明
我们将实现一个简单的股票价格发布系统,被观察者为股票,观察者为投资者。当股票价格发生变化时,所有投资者都会收到通知。
代码实现
// Observer接口
public interface Observer {void update(String stockName, double stockPrice);
}// Subject接口
public interface Subject {void attach(Observer o);void detach(Observer o);void notifyObservers();
}// ConcreteSubject类
import java.util.ArrayList;
import java.util.List;public class Stock implements Subject {private String name;private double price;private List<Observer> observers = new ArrayList<>();public Stock(String name, double price){this.name = name;this.price = price;}public void setPrice(double price){this.price = price;System.out.println(name + " 的价格已更新为: " + this.price);notifyObservers();}public double getPrice(){return this.price;}public String getName(){return this.name;}@Overridepublic void attach(Observer o){observers.add(o);}@Overridepublic void detach(Observer o){observers.remove(o);}@Overridepublic void notifyObservers(){for(Observer o : observers){o.update(this.name, this.price);}}
}// ConcreteObserver类
public class Investor implements Observer {private String name;public Investor(String name){this.name = name;}@Overridepublic void update(String stockName, double stockPrice){System.out.println("投资者 " + name + " 收到通知: " + stockName + " 的最新价格为 " + stockPrice);}
}// 客户端代码
public class ObserverPatternDemo {public static void main(String[] args) {Stock apple = new Stock("Apple", 150.00);Investor investor1 = new Investor("Tom");Investor investor2 = new Investor("Jerry");apple.attach(investor1);apple.attach(investor2);apple.setPrice(155.00);apple.setPrice(160.50);apple.detach(investor1);apple.setPrice(165.75);}
}
输出
Apple 的价格已更新为: 155.0 投资者 Tom 收到通知: Apple 的最新价格为 155.0 投资者 Jerry 收到通知: Apple 的最新价格为 155.0 Apple 的价格已更新为: 160.5 投资者 Tom 收到通知: Apple 的最新价格为 160.5 投资者 Jerry 收到通知: Apple 的最新价格为 160.5 Apple 的价格已更新为: 165.75 投资者 Jerry 收到通知: Apple 的最新价格为 165.75
代码说明
-
Observer接口:定义了
update()
方法,供被观察者调用以通知观察者。 -
Subject接口:定义了
attach()
,detach()
, 和notifyObservers()
方法,用于管理观察者和通知。 -
Stock类(ConcreteSubject):实现了 Subject 接口,维护了股票名称、价格和观察者列表。当股票价格变化时,调用
notifyObservers()
方法通知所有观察者。 -
Investor类(ConcreteObserver):实现了 Observer 接口,定义了
update()
方法,接收被观察者的通知并打印信息。 -
ObserverPatternDemo类:客户端,创建了被观察者(股票)和观察者(投资者),并演示了观察者的添加、通知和移除过程。
4.2. Python 实现示例
示例说明
同样,实现一个简单的股票价格发布系统,被观察者为股票,观察者为投资者。当股票价格发生变化时,所有投资者都会收到通知。
代码实现
from abc import ABC, abstractmethod# Observer抽象类
class Observer(ABC):@abstractmethoddef update(self, stock_name: str, stock_price: float):pass# Subject抽象类
class Subject(ABC):@abstractmethoddef attach(self, observer: Observer):pass@abstractmethoddef detach(self, observer: Observer):pass@abstractmethoddef notify_observers(self):pass# ConcreteSubject类
class Stock(Subject):def __init__(self, name: str, price: float):self._name = nameself._price = priceself._observers = []def set_price(self, price: float):self._price = priceprint(f"{self._name} 的价格已更新为: {self._price}")self.notify_observers()def get_price(self) -> float:return self._pricedef get_name(self) -> str:return self._namedef attach(self, observer: Observer):self._observers.append(observer)def detach(self, observer: Observer):self._observers.remove(observer)def notify_observers(self):for observer in self._observers:observer.update(self._name, self._price)# ConcreteObserver类
class Investor(Observer):def __init__(self, name: str):self._name = namedef update(self, stock_name: str, stock_price: float):print(f"投资者 {self._name} 收到通知: {stock_name} 的最新价格为 {stock_price}")# 客户端代码
def observer_pattern_demo():apple = Stock("Apple", 150.00)investor1 = Investor("Tom")investor2 = Investor("Jerry")apple.attach(investor1)apple.attach(investor2)apple.set_price(155.00)apple.set_price(160.50)apple.detach(investor1)apple.set_price(165.75)if __name__ == "__main__":observer_pattern_demo()
输出
Apple 的价格已更新为: 155.0 投资者 Tom 收到通知: Apple 的最新价格为 155.0 投资者 Jerry 收到通知: Apple 的最新价格为 155.0 Apple 的价格已更新为: 160.5 投资者 Tom 收到通知: Apple 的最新价格为 160.5 投资者 Jerry 收到通知: Apple 的最新价格为 160.5 Apple 的价格已更新为: 165.75 投资者 Jerry 收到通知: Apple 的最新价格为 165.75
代码说明
-
Observer抽象类:定义了
update()
方法,供被观察者调用以通知观察者。 -
Subject抽象类:定义了
attach()
,detach()
, 和notify_observers()
方法,用于管理观察者和通知。 -
Stock类(ConcreteSubject):实现了 Subject 抽象类,维护了股票名称、价格和观察者列表。当股票价格变化时,调用
notify_observers()
方法通知所有观察者。 -
Investor类(ConcreteObserver):实现了 Observer 抽象类,定义了
update()
方法,接收被观察者的通知并打印信息。 -
observer_pattern_demo函数:客户端,创建了被观察者(股票)和观察者(投资者),并演示了观察者的添加、通知和移除过程。
5. 观察者模式的适用场景
观察者模式适用于以下场景:
-
一对多的关系:一个被观察者需要通知多个观察者,如新闻发布系统、股票价格变动通知等。
-
对象间的松散耦合:被观察者和观察者之间不需要了解彼此的具体实现,只通过接口进行通信。
-
需要动态添加或移除观察者:在运行时可以根据需求增加或减少观察者,如订阅/取消订阅通知的用户。
-
需要广播通信:当被观察者状态变化时,需要向所有相关观察者广播通知。
-
实现发布-订阅机制:在系统中实现发布者与订阅者之间的通信,如事件驱动系统。
示例应用场景:
-
图形用户界面(GUI)系统:按钮点击事件通知相关的监听器。
-
事件驱动系统:系统中发生特定事件时,通知所有注册的事件处理器。
-
社交媒体平台:用户发布新内容时,通知所有关注者。
-
实时数据监控系统:数据变化时,通知所有监控模块。
-
MVC架构中的视图更新:模型(Model)变化时,视图(View)自动更新。
6. 观察者模式的优缺点
6.1. 优点
-
松散耦合:被观察者和观察者之间通过接口进行通信,降低了对象间的依赖关系。
-
动态响应:可以在运行时动态地添加或移除观察者,增强系统的灵活性。
-
支持广播通信:被观察者可以同时通知多个观察者,适用于一对多的通信场景。
-
提高系统的可扩展性:通过增加新的观察者类,可以轻松扩展系统功能,而无需修改被观察者类。
-
简化对象间的通信:观察者模式通过中介机制(被观察者)简化了对象间的直接通信。
6.2. 缺点
-
可能导致性能问题:当观察者数量较多时,被观察者在通知所有观察者时可能会影响系统性能。
-
可能导致系统复杂性增加:在某些情况下,过多的观察者会使系统的逻辑变得复杂,难以维护。
-
可能引发循环依赖:如果设计不当,观察者和被观察者之间可能会形成循环依赖,导致系统行为异常。
-
难以调试:由于观察者模式的动态通知机制,可能会使系统的行为难以预测和调试。
-
缺乏通知的顺序控制:观察者模式通常没有规定通知观察者的顺序,可能导致一些问题。
7. 观察者模式的常见误区与解决方案
7.1. 误区1:观察者过多导致系统性能下降
问题描述: 当被观察者拥有大量观察者时,每次状态变化都需要通知所有观察者,可能导致性能问题,尤其是在实时系统中。
解决方案:
-
优化通知机制:只通知那些真正需要更新的观察者,避免不必要的通知。
-
使用异步通知:通过异步方式通知观察者,减少同步操作对系统性能的影响。
-
批量处理:将多次状态变化合并为一次通知,减少通知次数。
-
限制观察者数量:根据系统需求合理限制观察者的数量,避免过度订阅。
7.2. 误区2:观察者和被观察者之间形成循环依赖
问题描述: 如果观察者在更新过程中再次修改被观察者的状态,可能导致循环依赖,甚至引发无限递归。
解决方案:
-
设计良好的更新逻辑:避免在观察者的
update()
方法中直接修改被观察者的状态。 -
引入状态标识:通过标识来判断是否需要继续通知,避免无限循环。
-
使用双向依赖管理:明确观察者和被观察者之间的依赖关系,避免循环引用。
7.3. 误区3:忽视观察者的异常处理
问题描述: 当某个观察者在更新过程中抛出异常时,可能影响被观察者和其他观察者的通知流程。
解决方案:
-
异常捕获:在被观察者的
notifyObservers()
方法中,捕获每个观察者的异常,确保一个观察者的异常不会影响其他观察者。 -
日志记录:记录观察者的异常信息,便于后续的调试和维护。
-
观察者的自我保护:在观察者的
update()
方法中,妥善处理可能的异常,避免将异常抛给被观察者。
8. 观察者模式的实际应用实例
8.1. 股票价格发布系统
示例说明
实现一个简单的股票价格发布系统,被观察者为股票,观察者为投资者。当股票价格发生变化时,所有投资者都会收到通知。
Java实现
(请参考观察者模式的Java实现示例)
Python实现
(请参考观察者模式的Python实现示例)
8.2. 图形用户界面(GUI)事件监听
示例说明
在GUI系统中,按钮点击、文本框内容变化等事件通常使用观察者模式来实现。各个组件作为观察者,监听特定事件并作出响应。
Java实现示例
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;// Observer接口
interface ButtonClickObserver {void onButtonClick(String message);
}// Subject类
class ButtonSubject {private List<ButtonClickObserver> observers = new ArrayList<>();public void attach(ButtonClickObserver observer){observers.add(observer);}public void detach(ButtonClickObserver observer){observers.remove(observer);}public void notifyObservers(String message){for(ButtonClickObserver observer : observers){observer.onButtonClick(message);}}public void clickButton(){// 模拟按钮点击事件notifyObservers("按钮被点击了!");}
}// ConcreteObserver类
class LabelUpdater implements ButtonClickObserver {private JLabel label;public LabelUpdater(JLabel label){this.label = label;}@Overridepublic void onButtonClick(String message){label.setText(message);}
}// 客户端代码
public class ObserverPatternGUI {public static void main(String[] args) {JFrame frame = new JFrame("观察者模式示例");frame.setSize(300, 200);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);JButton button = new JButton("点击我");JLabel label = new JLabel("按钮未被点击");frame.setLayout(null);button.setBounds(100, 50, 100, 30);label.setBounds(100, 100, 200, 30);frame.add(button);frame.add(label);// 创建被观察者和观察者ButtonSubject buttonSubject = new ButtonSubject();LabelUpdater labelUpdater = new LabelUpdater(label);buttonSubject.attach(labelUpdater);// 添加按钮点击事件监听button.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {buttonSubject.clickButton();}});frame.setVisible(true);}
}
输出
当用户点击按钮时,标签的文本会更新为“按钮被点击了!”
8.3. 实时数据监控系统
示例说明
在实时数据监控系统中,数据源(被观察者)实时更新数据,多个监控模块(观察者)需要即时接收并处理数据变化。
Python实现示例
from abc import ABC, abstractmethod
import time
import threading# Observer抽象类
class DataObserver(ABC):@abstractmethoddef update(self, data):pass# Subject类
class DataSource:def __init__(self):self._observers = []self._data = 0self._running = Falsedef attach(self, observer: DataObserver):self._observers.append(observer)def detach(self, observer: DataObserver):self._observers.remove(observer)def notify_observers(self):for observer in self._observers:observer.update(self._data)def start(self):self._running = Truethreading.Thread(target=self._run).start()def stop(self):self._running = Falsedef _run(self):while self._running:self._data += 1print(f"数据源更新数据: {self._data}")self.notify_observers()time.sleep(1)# ConcreteObserver类
class Display(DataObserver):def __init__(self, name):self.name = namedef update(self, data):print(f"显示器 {self.name} 显示数据: {data}")# 客户端代码
def observer_pattern_monitoring_demo():data_source = DataSource()display1 = Display("A")display2 = Display("B")data_source.attach(display1)data_source.attach(display2)data_source.start()# 运行5秒后停止time.sleep(5)data_source.stop()if __name__ == "__main__":observer_pattern_monitoring_demo()
输出
数据源更新数据: 1 显示器 A 显示数据: 1 显示器 B 显示数据: 1 数据源更新数据: 2 显示器 A 显示数据: 2 显示器 B 显示数据: 2 数据源更新数据: 3 显示器 A 显示数据: 3 显示器 B 显示数据: 3 数据源更新数据: 4 显示器 A 显示数据: 4 显示器 B 显示数据: 4 数据源更新数据: 5 显示器 A 显示数据: 5 显示器 B 显示数据: 5
代码说明
-
DataObserver抽象类:定义了
update()
方法,供被观察者调用以通知观察者。 -
DataSource类(Subject):实现了 Subject 抽象类,维护了数据和观察者列表。通过线程模拟实时数据更新,并在数据变化时通知所有观察者。
-
Display类(ConcreteObserver):实现了 DataObserver 抽象类,接收被观察者的通知并显示数据。
-
observer_pattern_monitoring_demo函数:客户端,创建了数据源和显示器对象,启动数据源的实时更新,并在5秒后停止。
8.4. 社交媒体平台的通知系统
示例说明
在社交媒体平台中,用户可以关注其他用户,当被关注的用户发布新内容时,所有关注者都会收到通知。
Java实现示例
// Observer接口
public interface Follower {void update(String userName, String content);
}// Subject接口
public interface User {void follow(Follower follower);void unfollow(Follower follower);void notifyFollowers();void postContent(String content);String getName();String getContent();
}// ConcreteSubject类
import java.util.ArrayList;
import java.util.List;public class ConcreteUser implements User {private String name;private String content;private List<Follower> followers = new ArrayList<>();public ConcreteUser(String name){this.name = name;}@Overridepublic void follow(Follower follower){followers.add(follower);}@Overridepublic void unfollow(Follower follower){followers.remove(follower);}@Overridepublic void notifyFollowers(){for(Follower follower : followers){follower.update(this.name, this.content);}}@Overridepublic void postContent(String content){this.content = content;System.out.println(this.name + " 发布新内容: " + this.content);notifyFollowers();}@Overridepublic String getName(){return this.name;}@Overridepublic String getContent(){return this.content;}
}// ConcreteObserver类
public class UserFollower implements Follower {private String followerName;public UserFollower(String followerName){this.followerName = followerName;}@Overridepublic void update(String userName, String content){System.out.println("关注者 " + followerName + " 收到通知: " + userName + " 发布了新内容 - " + content);}
}// 客户端代码
public class ObserverPatternSocialMediaDemo {public static void main(String[] args) {User alice = new ConcreteUser("Alice");User bob = new ConcreteUser("Bob");Follower follower1 = new UserFollower("Tom");Follower follower2 = new UserFollower("Jerry");Follower follower3 = new UserFollower("Lily");alice.follow(follower1);alice.follow(follower2);bob.follow(follower3);alice.postContent("Hello, this is Alice!");bob.postContent("Bob's first post.");alice.postContent("Alice's second post.");}
}
输出
Alice 发布新内容: Hello, this is Alice! 关注者 Tom 收到通知: Alice 发布了新内容 - Hello, this is Alice! 关注者 Jerry 收到通知: Alice 发布了新内容 - Hello, this is Alice! Bob 发布新内容: Bob's first post. 关注者 Lily 收到通知: Bob 发布了新内容 - Bob's first post. Alice 发布新内容: Alice's second post. 关注者 Tom 收到通知: Alice 发布了新内容 - Alice's second post! 关注者 Jerry 收到通知: Alice 发布了新内容 - Alice's second post!
代码说明
-
Follower接口(Observer):定义了
update()
方法,供被观察者调用以通知观察者。 -
User接口(Subject):定义了关注、取消关注、通知观察者和发布内容的方法。
-
ConcreteUser类(ConcreteSubject):实现了 User 接口,维护了用户名称、内容和关注者列表。当用户发布新内容时,通知所有关注者。
-
UserFollower类(ConcreteObserver):实现了 Follower 接口,接收被观察者的通知并打印信息。
-
ObserverPatternSocialMediaDemo类:客户端,创建了被观察者(用户)和观察者(关注者)对象,演示了关注、发布内容和通知的过程。
9. 观察者模式与其他模式的比较
9.1. 观察者模式 vs. 发布-订阅模式
-
观察者模式是一种对象之间的一对多的通信机制,通常在同一系统内部使用。
-
发布-订阅模式(Pub/Sub)是一种消息传递的模式,允许消息的发布者和订阅者解耦,通常通过中介者(如消息队列)进行通信,可以跨系统使用。
关键区别:
-
耦合度:观察者模式的观察者和被观察者之间存在直接的引用关系,而发布-订阅模式的发布者和订阅者通过中介者进行通信,耦合度更低。
-
应用范围:观察者模式适用于单一应用内部的通信,发布-订阅模式适用于分布式系统或跨系统的消息传递。
-
实现方式:观察者模式通常由被观察者维护观察者列表,发布-订阅模式通过消息代理实现消息的分发。
9.2. 观察者模式 vs. 策略模式
-
观察者模式用于建立对象之间的一对多的通信机制,当被观察者状态变化时,自动通知所有观察者。
-
策略模式用于定义一系列算法,并使得它们可以相互替换,算法的变化不会影响使用它的客户。
关键区别:
-
目的不同:观察者模式关注对象间的通信与通知,策略模式关注算法的封装与替换。
-
结构不同:观察者模式涉及被观察者和多个观察者的交互,策略模式涉及上下文和单个策略的替换。
-
应用场景不同:观察者模式适用于事件驱动的场景,策略模式适用于需要动态选择算法的场景。
9.3. 观察者模式 vs. 适配器模式
-
观察者模式用于建立对象之间的一对多的通信机制,自动通知观察者状态变化。
-
适配器模式用于将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而无法一起工作的类可以一起工作。
关键区别:
-
目的不同:观察者模式关注对象间的通信与通知,适配器模式关注接口的转换与兼容。
-
结构不同:观察者模式涉及被观察者和多个观察者,适配器模式涉及目标接口和适配者接口之间的转换。
-
应用场景不同:观察者模式适用于需要事件通知的场景,适配器模式适用于接口不兼容的类之间的集成。
9.4. 观察者模式 vs. 装饰者模式
-
观察者模式用于建立对象之间的一对多的通信机制,自动通知观察者状态变化。
-
装饰者模式用于动态地为对象添加新功能,通过装饰者对象包装原有对象,增强其功能。
关键区别:
-
目的不同:观察者模式关注对象间的通信与通知,装饰者模式关注对象功能的动态扩展。
-
结构不同:观察者模式涉及被观察者和多个观察者,装饰者模式涉及被装饰者和多个装饰者的层叠结构。
-
应用场景不同:观察者模式适用于事件驱动的场景,装饰者模式适用于需要动态增加对象功能的场景。
10. 观察者模式的扩展与变体
观察者模式在实际应用中可以根据需求进行一些扩展和变体,以适应不同的场景和需求。
10.1. 事件过滤器
在某些情况下,观察者可能只对特定类型的事件感兴趣。通过引入事件过滤器,观察者可以只接收自己关心的事件类型,避免不必要的处理。
实现方式:
-
事件类型标识:被观察者在通知时带上事件类型信息,观察者根据事件类型决定是否处理。
-
过滤器接口:定义一个过滤器接口,观察者可以实现该接口以指定感兴趣的事件类型。
-
通知机制调整:被观察者在通知观察者时,传递事件类型和相关数据。
10.2. 双向观察
在传统的观察者模式中,通信是单向的,即被观察者通知观察者。双向观察允许观察者在接收通知后,能够影响被观察者的状态。
实现方式:
-
双向引用:观察者持有对被观察者的引用,可以调用被观察者的方法。
-
更新策略调整:在观察者的
update()
方法中,可以执行对被观察者的修改操作。
注意事项:
-
避免循环依赖:确保观察者对被观察者的修改不会导致无限循环通知。
-
明确职责划分:观察者对被观察者的影响应有限,避免观察者承担过多职责。
10.3. 推模型与拉模型
观察者模式可以分为推模型(Push Model)和拉模型(Pull Model),根据通知方式的不同进行分类。
-
推模型:被观察者在通知观察者时,直接将数据推送给观察者。观察者在接收通知时,已经获得了更新的数据。
-
拉模型:被观察者在通知观察者时,只发送通知信号,观察者需要主动从被观察者获取更新的数据。
实现方式:
-
推模型:
update()
方法带有数据参数,被观察者在通知时传递更新的数据。 -
拉模型:
update()
方法不带数据参数,观察者在接收通知后,通过调用被观察者的方法获取最新数据。
优缺点对比:
-
推模型:效率更高,减少了通信次数,但增加了被观察者与观察者之间的数据依赖。
-
拉模型:更加灵活,观察者可以根据需要获取数据,但可能导致多余的数据获取。
10.4. 基于主题的观察者
在复杂系统中,一个被观察者可能涉及多个主题(主题可以理解为不同的事件或状态类别)。通过引入主题管理机制,观察者可以订阅感兴趣的特定主题,接收相关的通知。
实现方式:
-
主题分类:将被观察者的通知按主题分类。
-
主题管理:被观察者维护不同主题的观察者列表。
-
通知机制调整:在通知时指定主题,观察者根据订阅的主题接收通知。
优缺点对比:
-
优点:提高了通知的针对性,减少了观察者的无关通知。
-
缺点:增加了被观察者的复杂性,管理多个主题需要更复杂的逻辑。
11. 总结
观察者模式(Observer Pattern) 通过建立对象之间的一对多的通信机制,使得被观察者状态变化时,能够自动通知所有观察者,实现了对象间的松散耦合和灵活交互。该模式广泛应用于事件驱动系统、实时数据监控、用户界面组件交互等场景,提升了系统的可扩展性和维护性。
关键学习点回顾:
-
理解观察者模式的核心概念:建立一对多的通信机制,实现对象间的自动通知。
-
掌握观察者模式的结构:包括Subject、ConcreteSubject、Observer和ConcreteObserver之间的关系。
-
识别适用的应用场景:事件驱动系统、实时数据监控、用户界面组件交互等。
-
认识观察者模式的优缺点:松散耦合、动态响应、支持广播通信;但可能导致性能问题、系统复杂性增加、循环依赖等。
-
理解常见误区及解决方案:优化通知机制、避免循环依赖、加强异常处理等。
-
实际应用中的观察者模式实例:股票价格发布系统、GUI事件监听、实时数据监控、社交媒体通知系统等。
-
观察者模式的扩展与变体:事件过滤器、双向观察、推模型与拉模型、基于主题的观察者等。
相关文章:
23. 观察者模式
原文地址: 观察者模式 更多内容请关注:智想天开 1. 观察者模式简介 观察者模式(Observer Pattern)是一种行为型设计模式,用于建立对象之间的一种一对多的依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都…...
go的”ambiguous import in multiple modules”
执行“go mod tidy”报如下错误: go mod tidy -compat1.17 go: finding module for package github.com/gomooon/goredis go: found github.com/gomooon/goredis in github.com/gomooon/goredis v0.3.5 go: github.com/gomooon/core importsgithub.com/gomooon/gor…...
【鸿蒙开发】MongoDB入门
https://www.mongodb.com/try/download/community 下载MongoDB: var mongoose require("mongoose");// localhost 域名,代表本机 // 127.0.0.1 ip , 代码本机 mongoose.connect("mongodb://localhost:27017/jiaju").then(() > {console.l…...
【应用篇】MLU上deepseek/QwQ-32B+dify实现workflow应用
文章目录 前言一、平台环境选择二、创建容器应用三、启动服务1.下载deepseekR1-14B模型2.VLLM启动服务3.postman测试服务 四、workflow搭建1.搭建第一个工作流2.详细配置 五、效果演示 前言 本章主要讲解如何用paas平台,实现智能体应用 本章中大模型我们使用deeps…...
vue组件库el-menu导航菜单设置index,地址不会变更的问题
请先确认 1.路由已配置好 route-index.js如下, 2.view-ProHome.vue中已预留路由展示位 3.导航菜单复制组件库,并做修改 其中index与路由配置的地址一致 运行后发现点击菜单,url地址还是不变,查看组件库 Element - The worlds …...
防抖和节流
防抖(Debounce)和节流(Throttle)是前端开发中常用的两种性能优化技术,主要用于控制高频事件的触发频率,避免不必要的性能消耗。 1. 防抖(Debounce) 防抖的核心思想:在事…...
Deepseek可以通过多种方式帮助CAD加速工作
自动化操作:通过Deepseek的AI能力,可以编写脚本来自动化重复性任务。例如,使用Python脚本调用Deepseek API,在CAD中实现自动化操作。 插件开发:结合Deepseek进行二次开发,可以创建自定义的CAD插件。例如&a…...
基于Spring Boot的宠物猫认养系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
开源!速度100Kb/s的有线和无线双模ESP32S3芯片的DAP-Link调试器
开源!速度100Kb/s的有线和无线双模ESP32S3芯片的DAP-Link调试器 目录 开源!速度100Kb/s的有线和无线双模ESP32S3芯片的DAP-Link调试器本项目未经授权,禁止商用!本项目未经授权,禁止商用!本项目未经授权&…...
Vue3 模板引用:打破数据驱动的次元壁(附高阶玩法)
在数据驱动的Vue世界中,模板引用(Template Refs)是我们与真实DOM对话的秘密通道。本文将带你深入理解这个"逃生舱"的正确打开方式,并分享实战中的高阶技巧。 一、基础入门:建立DOM连接 1. 创建模板引用 &…...
第五天 Labview数据记录(5.5 SQL数据库读写)
5.5 SQL数据库读写 SQL 数据库读写操作是现代软件开发、数据分析和企业信息系统的核心功能。其意义不仅体现在技术层面,还涉及到业务流程优化、数据管理、决策支持等多个方面。以下是 SQL 数据库读写操作的重要意义:1. 数据存储与管理;2. 支…...

微信小程序项目引入图片问题:Error: module ‘assets/img/topImg.jpg.js‘ is not defined
问题与处理策略 问题描述 在微信小程序项目中,通过 require 引入图片文件,报如下错误 Error: module assets/img/topImg.jpg.js is not defined, require args is ../../assets/img/topImg.jpg# 翻译错误:未定义模块“assets/img/topImg.…...
02C#基本结构篇(D4_注释-访问修饰符-标识符-关键字-运算符-流程控制语句)
目录 一、注释 1. 单行注释 2. 多行注释 3. XML文档注释 4. 使用建议和最佳实践: 二、访问修饰符 1. public 2. private 3. protected 4. internal 5. protected internal 或 protected and internal 6. private protected 或 private and protected 7.…...
Python:正则表达式
正则表达式的基础和应用 一、正则表达式核心语法(四大基石) 1. 元字符(特殊符号) 定位符 ^:匹配字符串开始位置 $:匹配字符串结束位置 \b:匹配单词边界(如 \bword\b 匹配…...
ChatGPT4.5详细介绍和API调用详细教程
OpenAI在2月27日发布GPT-4.5的研究预览版——这是迄今为止OpenAI最强大、最出色的聊天模型。GPT-4.5在扩大预训练和微调规模方面迈出了重要的一步。通过扩大无监督学习的规模,GPT-4.5提升了识别内容中的模式、建立内容关联和生成对于内容的见解的能力,但…...
linux makefile tutorial
一个makefile的教程,几个小时就能看完,对makefile有个总体加细节的系统了解,非常不错: Learn Makefiles With the tastiest examples 中文翻译版: 起步 - Makefile 教程 (gavinliu6.github.io) gcc官网手册&#x…...
学习C2CRS Ⅲ (Response Generation Module)
代码地址:https://github.com/RUCAIBox/WSDM2022-C2CRS 论文地址:https://arxiv.org/abs/2201.02732 CFSelectionConvModel模型结构与功能 CFSelectionConvModel 是一个用于对话推荐系统的端到端模型,结合了知识图谱(KG)、评论信息和对话上下文来生成对话响应。它通过以…...
SpringBoot全栈开发:从数据库到Markdown文件导出的终极实践指南
一、SpringBoot后端核心实现 1.1 数据库数据转MD文件 通过SpringBoot实现数据库内容导出为Markdown文件,是文档自动化生成的关键技术: GetMapping("/download") public void exportMd(HttpServletResponse response, Integer id) {Content …...
go函数详解
1.简介 函数是组织好的、可重复使用的,用于执行指定任务的代码块,为了完成某一个功能的程序指令的集合,称为函数。go语言中支持:函数、匿名函数和闭包。 2.函数的定义 func 函数名 (形参列表) (返回值列表){ 函数体 return …...
MVCC实现原理
一、引言 在现代数据库管理系统中,数据的一致性和并发性是两个至关重要的特性。传统的锁机制虽然有效,但也存在着性能瓶颈,特别是在高并发环境下,锁的争用会导致系统响应时间变慢,甚至引发死锁等问题。为了克服这些挑…...
通过Golang的container/list实现LRU缓存算法
文章目录 力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2. 插入元素3. 删除元素4. 遍历链表5. 获取链表长度使用场景注意事项 源代码阅读 在 Go 语言中,container/list 包提供了一个双向链表的实现。链表是一种常见的数据结构&#…...
网络编程7天学java
* 网络编程:两台或两台以上的主机构成一个网络 * IP地址:标志网络中的一个通信实体的地址 * 端口号:区分不同应用程序 * 网络通信协议:ISO参考模型(7层),TCP/IP协议(4层)…...
在 IntelliJ IDEA 中配置 Git
1. 确保已安装 Git 在配置之前,确保你的系统已经安装了 Git。 检查是否已安装 Git: bash 复制 git --version 如果未安装,请前往 Git 官网 下载并安装。 2. 在 IntelliJ IDEA 中配置 Git 打开 IntelliJ IDEA。 进入设置: Windo…...
【Godot4.4】Rect2总结
概述 Rect2是2D场景中比较重要的一种数据类型。 Rect2的本质含义是2D场景的轴对齐包围盒,而不是可以自由变换的矩形。 Rect2提供了一些方法,可以方便的判断Rect2之间是否重叠、包含等,并可以获得重叠的区域。也可以获得两个Rect2的包围盒。…...
git使用命令总结
文章目录 Git 复制创建提交步骤Git 全局设置:创建 git 仓库:已有仓库? 遇到问题解决办法:问题一先git pull一下,具体流程为以下几步: 详细步骤 Git 复制 git clone -b RobotModelSetting/develop https://gitlab.123/PROJECT/123.git创建提…...
Unity DOTS从入门到精通之 C# Job System
文章目录 前言安装 DOTS 包C# 任务系统Mono 环境DOTS 环境运行作业NativeContainer 前言 作为 DOTS 教程,我们将创建一个旋转立方体的简单程序,并将传统的 Unity 设计转换为 DOTS 设计。 Unity 2022.3.52f1Entities 1.3.10 安装 DOTS 包 要安装 DOTS…...
linux下的网络抓包(tcpdump)介绍
linux下的网络抓包[tcpdump]介绍 前言tcpdump1. 安装 tcpdump2. 基本抓包命令3. 过滤器使用4. 保存捕获的数据包 异常指标1. 连接建立与断开相关指标异常 SYN 包异常 FIN 或 RST 包 2. 流量相关指标异常流量峰值异常源或目的 IP 流量 3. 端口相关指标异常端口使用端口扫描 4. 数…...
深入理解 Linux 中的 -h 选项:让命令输出更“人性化”
在 Linux 系统中,命令行工具是系统管理员和普通用户最常用的交互方式之一。然而,命令行输出往往充满了技术性术语和数字,对于初学者或非技术用户来说可能显得晦涩难懂。幸运的是,许多 Linux 命令都提供了一个非常实用的选项&#…...
selenium的鼠标操作
1、鼠标操作 鼠标时间对应的方法在那个类中? ActionChains类,实例化 鼠标对象 1、context_click(element) # 右击 2、double_click(element) #双击 3、double_and_drop(source, target) # 拖拽 4、move_to_element(element) # 悬停 【重点】 5、perform() …...
STM32——GPIO介绍
GPIO(General-Purpose IO ports,通用输入/输出接口)模块是STM32的外设接口的核心部分,用于感知外界信号(输入模式)和控制外部设备(输出模式),支持多种工作模式和配置选项。 1、GPIO 基本结构 STM32F407 的每个 GPIO 引脚均可独立配置,主要特性包括: 9 组 GPIO 端口…...
Word 小黑第15套
对应大猫16 修改样式集 导航 -查找 第一章标题不显示 再选中文字 点击标题一 修改标题格式 格式 -段落 -换行和分页 勾选与下段同页 添加脚注 (脚注默认位于底部 )在脚注插入文档属性: -插入 -文档部件 -域 类别选择文档信息,域…...
linux自启动服务
在Linux环境中,systemd是一个系统和服务管理器,它为每个服务使用.service文件进行配置。systemctl是用于控制系统服务的主要工具。本文将详细介绍如何使用systemctl来管理vsftpd服务,以及如何设置服务自启动。 使用Systemd设置自启动服务 创…...
react使用拖拽,缩放组件,采用react-rnd解决 -完整版
屏幕录制2025-03-10 10.16.06 以下代码仅提供左侧可视化区域 右侧数据根据你们的存储数据来 大家直接看Rnd标签设置的属性即可!!!!! /*** 用户拖拽水印的最终位置信息*/ export interface ProductWatermarkValue {wat…...
通过 ElasticSearch的Python API和`curl` 命令获取Elasticsearch 所有索引名称
导言 在大数据管理和实时搜索场景中,Elasticsearch 是一款不可或缺的工具。无论是开发调试、数据维护,还是系统监控,快速列出所有索引名称都是一个高频需求。本文将手把手教你如何通过 Python 客户端连接 Elasticsearch,并用两种方…...
Flutter:StatelessWidget vs StatefulWidget 深度解析
目录 1. 引言 2. StatelessWidget(无状态组件) 2.1 定义与特点 2.2 代码示例 3. StatefulWidget(有状态组件) 3.1 定义与特点 3.2 代码示例 4. StatelessWidget vs StatefulWidget 对比 5. StatefulWidget 生命周期 5.1…...
[密码学实战]Java实现国密TLSv1.3单向认证
一、代码运行结果 1.1 运行环境 1.2 运行结果 1.3 项目架构 二、TLS 协议基础与国密背景 2.1 TLS 协议的核心作用 TLS(Transport Layer Security) 是保障网络通信安全的加密协议,位于 TCP/IP 协议栈的应用层和传输层之间,提供: • 数据机密性:通过对称加密算法(如 AE…...
蓝桥杯省赛真题C++B组2024-握手问题
一、题目 【问题描述】 小蓝组织了一场算法交流会议,总共有 50 人参加了本次会议。在会议上,大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进行一次握手(且仅有一次)。但有 7 个人,这 7 人彼此之间没有进行握手(但这…...
项目实操分享:一个基于 Flask 的音乐生成系统,能够根据用户指定的参数自动生成 MIDI 音乐并转换为音频文件
在线体验音乐创作:AI Music Creator - AI Music Creator 体验者账号密码admin/admin123 系统架构 1.1 核心组件 MusicGenerator 类 负责音乐生成的核心逻辑 包含 MIDI 生成和音频转换功能 管理音乐参数和音轨生成 FluidSynth 集成 用于 MIDI 到音频的转换 …...
Java开发者如何接入并使用DeepSeek
目录 一、准备工作 二、添加DeepSeek SDK依赖 三、初始化DeepSeek客户端 四、数据上传与查询 五、数据处理与分析 六、实际应用案例 七、总结 【博主推荐】:最近发现了一个超棒的人工智能学习网站,内容通俗易懂,风格风趣幽默ÿ…...
多方安全计算(MPC)电子拍卖系统
目录 一、前言二、多方安全计算(MPC)与电子拍卖系统概述2.1 多方安全计算(MPC)的基本概念2.2 电子拍卖系统背景与需求三、MPC电子拍卖系统设计原理3.1 系统总体架构3.2 电子拍卖中的安全协议3.3 数学与算法证明四、数据加解密模块设计五、GPU加速与系统性能优化六、GUI设计与系…...
【数据库系统概论】第十一章 并发控制
第十一章 并发控制 11.1 并发控制概述(1)丢失修改(2)不可重复读(3)读“脏”数据 11.2 封锁11.2.1 封锁的概念11.2.2 基本封锁类型(1)排它锁/X锁/写锁(2)共享锁…...
C语言_数据结构总结4:不带头结点的单链表
纯C语言代码,不涉及C 0. 结点结构 typedef int ElemType; typedef struct LNode { ElemType data; //数据域 struct LNode* next; //指针域 }LNode, * LinkList; 1. 初始化 不带头结点的初始化,即只需将头指针初始化为NULL即可 void Init…...
用CSS画一条0.5px的线
上次面试前端被问到了这个问题,感觉有点懵懵的,我就回答了一个scaleY(0.5),这个是真的没想到,希望有需要的朋友可以去看看。随便记住一种就行。 1.第一种方式:通过缩放1px的线条实现视觉上的0.5px效果,兼容性较好。 …...
知识库全链路交互逻辑
阶段顺序 URL输入 → 网络连接 → 前端请求 → 后端处理 → 数据库交互 → 数据返回 → 前端渲染 → 连接关闭 阶段1:用户输入URL 用户行为:在浏览器地址栏输入 https://knowledge.com/search?keyword金融趋势 浏览器动作: “浏览器解析U…...
BambuStudio学习笔记:Model
# Model.hpp 核心模型结构说明## 文件概述 该头文件定义了3D打印数据处理的核心数据结构,包含模型对象、体积、实例、材料等关键类。主要功能包括: - 三维模型数据存储与管理 - 模型变换操作(平移/旋转/缩放) - 打印参数配置 - 多…...
Spring (八)AOP-切面编程的使用
目录 实现步骤: 1 导入AOP依赖 2 编写切面Aspect 3 编写通知方法 4 指定切入点表达式 5 测试AOP动态织入 图示: 一 实现步骤: 1 导入AOP依赖 <!-- Spring Boot AOP依赖 --><dependency><groupId>org.springframewor…...
【Go每日一练】构建一个简单的用户信息管理系统
👻创作者:丶重明 👻创作时间:2025年3月7日 👻擅长领域:运维 目录 1.😶🌫️题目:简单的用户信息管理系统2.😶🌫️代码开发3.😶&a…...
PathRAG:通过图剪枝的方法优化Graph-based RAG的性能方法浅析
PathRAG 也是一种新型 Graph-based RAG 方法,通过检索索引图中的关键关系路径,减少噪声并优化 LLM 提示。其核心创新在于基于流的剪枝算法和路径为基础的提示策略,特别适用于捕捉复杂数据集中的关系。(其实可以看做相比GraphRAG假…...
ElementUI 级联选择器el-cascader启用选择任意一级选项,选中后关闭下拉框
1、启用选择任意一级选项 在 el-cascader 标签上加上配置项: :props"{ checkStrictly: true }"例如: <el-cascaderref"selectedArrRef"v-model"selectedArr":options"optionsList":props"{ checkStri…...
【软件逆向】QQ 连连看小游戏去广告与一键消除实现
目录 一、背景介绍 二、去广告实现 2.1 分析广告加载流程 2.2 逆向分析广告加载逻辑 2.3 去广告方案 三、一键消除外挂实现 3.1 分析游戏逻辑 3.2 编写外挂插件 3.3 注入外挂: 四、一键消除效果展示 五、额外扩展 一、背景介绍 QQ 连连看是一款经典的休闲…...