[设计模式] —— Observer 观察者模式

Observer 观察者模式

组件协作模式通过晚绑定,来实现框架与应用程序之间的松耦合。是框架和引用程序协作常用的。

动机

我们需要为一些对象建立一种“通知依赖”关系,即一个对象状态发生变化,所有的依赖对象(观察者对象)都将得到通知,如果依赖过于紧密,软件不能很好地抵御变化。面向对象技术可以使这种依赖关系弱化,并形成一种稳定的依赖关系,从而体系结构会松耦合。

定义

对象间一种一对多(变化)的依赖关系,以便当一个对象的状态(目标对象)发生变化时,所有依赖它的对象都得到通知并自动更新。

示例代码
class FilterSplitter {
private:
 string m_filePath;
 int m_fileNumber;
public:
 FilterSplitter(cons string& filePath, int fileNumber) : 
  m_filePath(filePath), m_fileNumber(fileNumber) {}

 void split() {
     // 1. 读取文件
     // 2. 分批次向小文件写入
     for(int i = 0; i< m_fileNumber; ++i) {
         //...
     }
 }
 };

class MainForm : public Form {
private:
  TextBox* txtFilePath;
  TextBox* txtFileNumber;
 public:
  void Button1_Click() {
      string filePath = txtFilePath->getText();
      int number = atoi(txtFileNumber->getText().c_str());
      FilterSplitter splitter(filePath, number);
      splitter.split();
  }
 };

假设现在有这样一个文件分割功能的代码,其中 FilterSplitter 类主要负责文件的切割,MainForm 则是直接触发文件分割的类。现在我们需求发生变化,想要在分割文件的同时,可以将分割进度条展现出来,有一个新的类 ProgressBar 可做这个事。可能比较常规的一种做法是,将 ProgressBar 作为参数传入 FilterSplitter 类中,方便调用其成员函数。

class FilterSplitter {
private:
 string m_filePath;
 int m_fileNumber;
 ProgressBar * m_progressBar; // 新增代码

public:
 FilterSplitter(cons 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_progress != nullptr) {
         float progressValue = m_fileNumber;
         progressValue = (i+1) / progressValue;
         m_progressBar->setValue(progressValue); // 更新并显示进度条
       }
     }
 }
 };

class MainForm : public Form {
private:
  TextBox* txtFilePath;
  TextBox* txtFileNumber;
  ProgressBar *progressBar; // 新增代码
 public:
  void Button1_Click() {
      string filePath = txtFilePath->getText();
      int number = atoi(txtFileNumber->getText().c_str());
      FilterSplitter splitter(filePath, number, progressBar); // 新增代码
      splitter.split();
  }
 };

可以看到我们分别在 MainForm 界面类和 FilterSplitter 文件分割类中引入了新的界面相关的进度条类 ProgressBar,虽然这样可以满足我们的功能要求,但是这样就依赖了一个具体的进度条类 ProgressBar,试想一下,现在需求又变了,我们需要用更换另一种数字显式进度的方式,那么我们就需要修改 FilterSplitter 涉及到 ProgressBar 部分的源码。

首先这是违背了依赖倒置原则的,即依赖抽象而不是细节,ProgressBar 对我们来说就是细节,其次有需求变更就要修改源码也违背了开闭原则。如果想着去找通知进度相关的基类,但是其进度更新子类的接口可能是不统一的,ProgressBar 是 setValue,其他的子类可能自己实现的是 updateValue 接口。所以对于代码优化单纯的去找父类去实现依赖抽象,有的时候也未必合适

这里 ProgressBar 主要是起到了一个通知的作用,更好地方式是可以进一步将这个功能抽象成一个基类,这个接口基类统一了更新数值的接口,并且有界面类 MainForm 继承接口并实现它,然后对 FilterSplitter 类进行修改,通过依赖抽象基类来松耦合。

class IProgress { // 接口基类
public: 
 virtual void DoProgress(float value) = 0;
 ~IProgress(){}
};

class FilterSplitter {
private:
 string m_filePath;
 int m_fileNumber;
 // ProgressBar * m_progressBar; // 依赖具体
 IProgress * m_progress; // 抽象机制

public:
 FilterSplitter(cons string& filePath, int fileNumber, IProgress* iProgress) : 
  m_filePath(filePath), m_fileNumber(fileNumber), m_progress(iProgress) {} // 新增代码

 void split() {
     // 1. 读取文件
     // 2. 分批次向小文件写入
     for(int i = 0; i< m_fileNumber; ++i) {
         //...

         // 新增代码
       float progressValue = m_fileNumber;
       progressValue = (i+1) / progressValue;
       onProgress(progressValue); // 提炼函数
     }
 }
protected:
 void onProgress(float progressValue) {
   if (m_progress != nullptr) {
     m_progress->DoProgress(progressValue); // 通知更新进度
    }
   }
 };

class MainForm : public Form, public IProgress{ // 这里是多继承
private:
  TextBox* txtFilePath;
  TextBox* txtFileNumber;
  ProgressBar *progressBar; // 新增代码
 public:
  void Button1_Click() {
      string filePath = txtFilePath->getText();
      int number = atoi(txtFileNumber->getText().c_str());
    	// 因为是多继承,可以直接传 MainForm 类,这里就是 this 指针
      FilterSplitter splitter(filePath, number, this); 
      splitter.split();
  }
	
  // 在 FilterSplitter 中通过 this 指针,真正调用的就是这里的实现
  void DoPregress(float value) {
      progressBar->setValue(value);
  }
 };

这个只是单个对象的通知,同样还可以通过多个对象的通知,进一步提供相关增删的接口即可:

class IProgress { // 接口基类
public:
 virtual void DoProgress(float value) = 0;
 ~IProgress(){}
};

class FilterSplitter {
private:
 string m_filePath;
 int m_fileNumber;
 List<IProgress *> m_progressList; // 抽象机制,支持多个观察者

public:
 FilterSplitter(cons string& filePath, int fileNumber) : 
  m_filePath(filePath), m_fileNumber(fileNumber) {} // 新增代码

 void split() {
     // 1. 读取文件
     // 2. 分批次向小文件写入
     for(int i = 0; i< m_fileNumber; ++i) {
         //...

         // 新增代码
        float progressValue = m_fileNumber;
        progressValue = (i+1) / progressValue;
        onProgress(progressValue);
     }
 }
 // 增加观察者
 void addIProgress(IProgress* iprogress) {
     m_progressList.push_back(iprogress);
 }
 // 消除观察者
 void removeIProgress(IProgress* iprogress) {
     m_progressList.remove(iprogress);
 }

protected:
 void onProgress(float progressValue) {
     // 遍历通知更新进度
    List<IProgress *>::iterator itor = m_progressList.begin();
    for(;itor!=m_progressList.end();++itor) {
        (*itor)->DoProgress(progressValue); 
    }
 }
 
 };

// 新增通知方式
class OtherNotifier : public IProgress {
public:
 void DoProgress(float value) {
     cout << "#";
 }
}

class MainForm : public Form, public IProgress{
private:
  TextBox* txtFilePath;
  TextBox* txtFileNumber;
  ProgressBar *progressBar; // 新增代码
 public:
  void Button1_Click() {
      string filePath = txtFilePath->getText();
      int number = atoi(txtFileNumber->getText().c_str());
      FilterSplitter splitter(filePath, number); // 因为是多继承,可以直接传 MainForm 类
      
      OtherNotifier otherNotifier;     
      splitter.addIProgress(this); // 订阅通知
      splitter.addIProgress(&otherNotifier); // 订阅通知

      splitter.split();
      splitter.removeIProgress(this); // 取消订阅
  }

  void DoPregress(float value) {
      progressBar->setValue(value);
  }
 };

以上就是通过接口抽象基类,以及多继承完成的观察者模式示例代码。

结构图
总结
  • Observer 观察者模式使我们可以独立地改变目标与观察者,从而使二者达到松耦合。
  • 目标发出通知时,无需指定观察者,通知会自动传播
  • 观察者自己决定是否需要订阅通知,目标对象对此一无所知

其他设计模式汇总
[设计模式] —— 设计模式的介绍及分类

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值