- 观察者模式也可以成为订阅者模式
- 如何实现状态变化后,所有的依赖对象(订阅者)都能得到更新
缘起 Motivation
- 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”
- 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知
- 如果这样的依赖关系过于紧密, 将使软件不能很好地抵御变化
- 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系
- 实现软件体系结构的松耦合
文件分割器
初级设计
界面
class MainForm : public Form
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
// [修改] 新需求:增加进度条
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
// [修改] 新需求:增加进度条
FileSplitter splitter(filePath, number, progressBar);
splitter.split();
}
};
分割器
class FileSplitter
{
string m_filePath;
int m_fileNumber;
// [修改] 新需求:增加进度条
ProgressBar* m_progressBar;
public:
FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
m_filePath(filePath),
m_fileNumber(fileNumber),
// [修改] 新需求:增加进度条
m_progressBar(progressBar){
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
// [修改] 新需求:增加进度条
if (m_progressBar != nullptr)
m_progressBar->setValue((float)(i+1)/m_fileNumber);
}
}
};
问题(8大设计原则)
- 依赖倒置
- 高层不依赖底层
- 高层FileSplitter依赖了底层实现细节prograssBar
- 进度展示方式可以有进度条、时钟、百分比、
###
等多种方式展现 - 实现细节就是有需求可以扩展的点
- 应对:
- 单纯找prograssBar的基类ControlBase控件(可能是MFC就有的控件吧),但这个基类可能没有setVal这个方法
- 再抽象一点,progressBar本质是一个通知机制
- 抽象不依赖细节
- 高层不依赖底层
依赖(编译时依赖):A依赖B,A需要编译时,B必须存在才能编译通过
重构设计
通知机制
class IProgress{
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress(){}
};
- I: Interface接口
- 我们希望通过IProgress来表达抽象的通知机制
- 之前的progressBar是一个通知控件
新的文件分割器
- 把原来progressBar控件换成IProgress通知机制
- 到此FileSplitter没有耦合任何界面类
- 可以独立编译
class FileSplitter
{
string m_filePath;
int m_fileNumber;
// 抽象通知机制,支持多个观察者
List<IProgress*> m_iprogressList;
public:
FileSplitter(const string& filePath, int fileNumber
IProgress* iprogress) :
m_filePath(filePath),
m_fileNumber(fileNumber)
// 通知机制初始化
m_iprogress(iprogress){
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
if (m_iprogress != nullprt) {
m_iprogress.DoProgress((i+1)/m_filenumber); // 更新通知
}
}
}
主函数mainForm
- MainForm要继承通知机制IProgress
- Cpp只推荐一种多继承
- 一个主继承类
- 其他都是接口
class MainForm : public Form, public IProgress {
TextBox* txtFilePath;
TextBox* txtFileNumber;
// 通知机制 [多态]
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, this);
splitter.split();
}
virtual void DoProgress(float value){
progressBar->setValue(value);
}
}
进一步优化
split() {
for (int i = 0; i < m_fileNumber; i++){
if (m_iprogress != nullptr) {
m_iprogress.DoProgress((i+1)/m_filenumber); // 更新通知
}
}
}
优化为:
split() {
for (int i = 0; i < m_fileNumber; i++){
onProgress(float(i+1)/m_fileNumber); //发送通知
}
}
}
protected:
virtual void onProgress(float value) {
if (m_iprogress != nullptr) {
m_iprogress->DoProgress(value); //更新进度条
}
}
如果要支持多个观察者
一个对象的状态改变,要通知所有的观察者
FileSpliter
class FileSplitter {
string m_filePath;
int m_fileNumber;
// 抽象通知机制,支持多个观察者
List<IProgress *> m_iprogressList;
// 修改初始化方式
public:
FileSplitter(const string &filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber) {
}
// 增加、删除iprogress方法
void addIProgress(IProgress *iprogress) {
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress *iprogress) {
m_iprogressList.remove(iprogress);
}
// 不变
void split() {
// ....
}
// 修改当前状态
protected:
virtual void onProgress(float value) {
List<IProgress *>::iterator itor = m_iprogressList.begin();
while (itor != m_iprogressList.end())
(*itor)->DoProgress(value); //随着进展,更新进度条
itor++;
}
}
主函数mainForm
主函数mainForm可以增加多个观察者(订阅者)
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
// 增加控制器观察者
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this); //订阅通知
splitter.addIProgress(&cn); //订阅通知
splitter.split();
splitter.removeIProgress(this);
}
控制器订阅者
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};
模式定义
- 定义对象间的一种一对多(变化)的依赖关系
- 当一个对象(Subject)的状态发生改变时
- 所有依赖于它的对象都得到通知并自动更新
结构
- 这个类图中Subject被单独提出来
- 本代码中没有从ConcreteSubject中抽象提出Subject
要点总结
- 使用面向对象的抽象
- Observer模式使得我们可以独立地改变目标与观察者
- 独立:松耦合
- MainForm中可以随意添加订阅者
- FileSplitter不需要修改都可以支持
- 从而使二者之间的依赖关系达致松耦合
- Observer模式使得我们可以独立地改变目标与观察者
- 目标发送通知时
- 无需指定观察者
- 由OnProgess()来发送通知
- 不需要知道谁是订阅者
- 而是针对整个订阅者列表
- 通知(可以携带通知信息作为参数)会自动传播
- 按照各个订阅者自己实现的方式传播(展示)订阅的内容
- 无需指定观察者
- 订阅者自己决定是否需要订阅通知
- 目标对象对此一无所知
- Observer模式是基于事件的UI框架中非常常用的设计模式
- 也是 MVC模式的一个重要组成部分
Rust 实现
功能说明
- 热水器
- 持续加热水温
- 水温变化时,通知各个观察者
- 观察者:洗澡水温
- 当水温小于30度时,显示:水温太冷, 不能洗澡
- 当水温在[30, 50)时,显示:水温正好,可以洗澡
- 当水温在[50, 70) 时,显示:水温太热,不能洗澡
- 观察者:烧开水
- 当水温小于100度时,显示:水未烧开
- 当水温大于100度时,显示:水已烧开
初始设计
#![feature(non_ascii_idents)]
trait IObserver {
fn update(&self, subject: Box<&dyn ISubject>);
}
trait ISubject {
fn add_observer(&mut self, observer: Box<dyn IObserver>);
fn remove_observer(&mut self, observer: Box<dyn IObserver>);
fn notify(&self);
fn get_state(&self) -> u32;
fn set_state(&mut self, state: u32);
}
struct 热水器 {
tmpr: u32,
observers: Vec<Box<dyn IObserver>>,
}
impl 热水器 {
fn new() -> Self {
Self {
observers: Vec::new(),
tmpr: 0,
}
}
}
impl ISubject for 热水器 {
fn add_observer(&mut self, observer: Box<dyn IObserver>) {
self.observers.push(observer);
}
fn remove_observer(&mut self, _observer: Box<dyn IObserver>) {
unimplemented!()
/*
由于Rust不支持抽象类型的向下具体化,所以这个思路无法实现
if let Some(idx) = self.observers.iter().position(|&ob| ob == observer) {
self.observers.remove(idx);
}
*/
}
fn notify(&self) {
for observer in self.observers.iter() {
observer.update(Box::new(self));
}
}
fn set_state(&mut self, tmpr: u32) {
self.tmpr = tmpr;
println!("\n[热水器] 温度: {}", self.tmpr);
self.notify();
}
fn get_state(&self) -> u32 {
self.tmpr
}
}
#[derive(PartialEq)]
struct 洗澡;
impl IObserver for 洗澡 {
fn update(&self, subject: Box<&dyn ISubject>) {
let tmpr = subject.get_state();
if tmpr < 30 {
println!("[洗澡] 水温太冷,不能洗澡");
} else if 30 <= tmpr && tmpr < 50 {
println!("[洗澡] 水温正常,可以洗澡");
} else if 50 <= tmpr && tmpr < 70 {
println!("[洗澡] 水温太热,不能洗澡");
}
}
}
#[derive(PartialEq)]
struct 开水壶;
impl IObserver for 开水壶 {
fn update(&self, subject: Box<&dyn ISubject>) {
let tmpr = subject.get_state();
if tmpr >= 100 {
println!("[开水壶] 水已烧开");
} else {
println!("[开水壶] 水未烧开");
}
}
}
fn main() {
let mut 热水器 = 热水器::new();
热水器.add_observer(Box::new(开水壶));
热水器.add_observer(Box::new(洗澡));
for tmpr in 0..12 {
热水器.set_state(tmpr * 10);
}
}