设计模式5——观察者模式(Observer)

观察者模式

动机

在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象的状态改变,所有的依赖对象(观察者对象)都能将得到通知.如果这样的依赖关系过于紧密,将使软件不能很好的抵御变化。
使用面向对象的技术,将这种依赖关系弱化,形成稳定的依赖关系,实现松耦合。

模式定义

定义一种对象之间的一对多的依赖关系,以便当一个对象的状态改变时,所有依赖于它的对象都得到通知并自动更新。
——《设计模式》GoF

例子

要实现一个文件分割的功能,就是将一个大的文件分割为几个小的文件;
在窗口处理程序中,点击Button1将会创建splitter对象,并调用分割程序split;
MainForm还有一个显示当前进度的功能,在类中有ProgressBar*,并将其传入Splitter对象,Splitter对象根据自己进度调用progressbar来显示进度。

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());
      FileSplitter splitter(filePath, number, progressBar);
      splitter.split();
   }
};

FileSplitter对象中,根据传入的参数调用构造函数,在此类中还必须有一个与MainFrom对象对应的ProgressBar类型的对象。

class FileSplitter
{
private:
   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++){
      //...
      float progressValue = m_fileNumber;
      progressValue = (i + 1) / progressValue;
      m_progressBar->setValue(progressValue);
  }
 }
};

然后根据分割的进度调用ProgressBar对象。
那么问题是什么呢?
问题就是MainFrom中若要更改显示进度的方式,不在使用ProgressBar类型对象,或者添加一种显示进程的方式,那么FileSplitter中也必须更改内容,出现了紧耦合。

改进版

首先将调用进度条显示,声明基类纯虚函数,DoProgress就是进程显示函数

class IProgress{
public:
    virtual void DoProgress(float value)=0;
    virtual ~IProgress(){}
};

下面是两种进度显示的方式,第一种类似于控制台应用程序,第二种就是Windows自己的的进度条对象

//类似于控制台应用程序打点显示进程
class ConsoleNotifier : public IProgress {
public:
   virtual void DoProgress(float value){
        cout << ".";
   }
};
//进度条显示
class Probar : public IProgress {
public:
    ProgressBar* progressBar;
    virtual void DoProgress(float value){
         progressBar->setValue(value);
    }
};

MainFrom就更改为:

class MainForm : public Form, public IProgress
{
private:
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
public:
    void Button1_Click(){
       string filePath = txtFilePath->getText();
       int number = atoi(txtFileNumber->getText().c_str());
       ProgressBar* progressBar;//一种进度显示对象
       ConsoleNotifier cn;//另一种进度显示对象
       FileSplitter splitter(filePath, number);//构造文件分割对象
       splitter.addIProgress(this); //将进度显示方式添加到该对象
       splitter.addIProgress(&cn)//将进度显示方式添加到该对象
       splitter.split();//开始分割
       splitter.removeIProgress(this);
 };

注意这里addIProgress是因为splitter类用一个list存储多个进度显示对象。
文件分割类的内容如下:

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){
    }
    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_iprogressList.push_back(iprogress);
 }
 void removeIProgress(IProgress* iprogress){//删除进度显示方式
 	 m_iprogressList.remove(iprogress);
 }
protected:
     virtual void onProgress(float value){  
         List<IProgress*>::iterator itor=m_iprogressList.begin();
 	 while (itor != m_iprogressList.end() )
   		  (*itor)->DoProgress(value); //更新进度条
  		 itor++;
  	}
     }
};

Splitter对象是根据已经分割成小文件的数量显示进度的,当调用
addIProgress(IProgress* iprogress)时,向List<IProgress*> m_iprogressList添加进度显示方式,而且是基类指针指向子类对象,所以可以多态调用进度显示。
文件分割时通过 onProgress(progressValue)依次更新进度条。


通过以上方式,进度显示将实现松耦合,根据需要往Splitter内添加进度显示方式。当然也可以不使用list这种方式。

总结

  1. 使用面向对象的抽象,Observer模式使得我们可以独立的改变目标(FileSplitter)和观察者(MainFrom),从而使二者的依赖关系松耦合。

  2. 目标发送通知,无需指定观察者,通知会自动传播。

  3. 观察者可以自己决定是否需要订阅通知(MainFrom中addProgress),目标对象对此一无所知(FileSplitter只根据LIST的基类指针依次调用)。

  4. 观察者模式是基于事件的UI框架中非常常用的设计模式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值