• 设计模式


    观察者模式(Observer / Event)是组件协作模式的一种。

    动机

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

    代码示例:文件分割器

     1 class FileSplitter
     2 {
     3     string m_filePath;
     4     int m_fileNumber;
     5     ProgressBar* m_progressBar;  //通知控件(实现细节),产生了编译时依赖
     6 
     7 public:
     8     FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
     9         m_filePath(filePath), 
    10         m_fileNumber(fileNumber),
    11         m_progressBar(progressBar){
    12 
    13     }
    14 
    15     void split(){
    16 
    17         //1.读取大文件
    18 
    19         //2.分批次向小文件中写入
    20         for (int i = 0; i < m_fileNumber; i++){
    21             //...
    22             float progressValue = m_fileNumber;
    23             progressValue = (i + 1) / progressValue;  //更新进度条
    24             m_progressBar->setValue(progressValue);
    25         }
    26 
    27     }
    28 };
    FileSplitter1.cpp
     1 class MainForm : public Form  //MainForm主窗口界面
     2 {
     3     TextBox* txtFilePath;    //要分割的文件的路径
     4     TextBox* txtFileNumber;  //要分割成子文件的个数
     5     ProgressBar* progressBar;//分割过程的进度条
     6 
     7 public:
     8     void Button1_Click(){
     9 
    10         string filePath = txtFilePath->getText();
    11         int number = atoi(txtFileNumber->getText().c_str());
    12         //实例化一个文件分割器对象,并初始化
    13         FileSplitter splitter(filePath, number, progressBar);
    14 
    15         splitter.split();
    16 
    17     }
    18 };
    MainForm1.cpp

     由于有时文件比较大,新增需求:需要看到文件分割的进度,就在MainForm添加一个子控件processBar

    违背了“依赖倒置原则”

     在类FileSplitter中,新增的通知控件m_processBar就是实现细节,当需求变更时,实现细节也会变(可能会以百分比显示,省略号显示等)。因此需求变化时,就产生了编译时依赖。

    如何重构以上代码?

     1 class IProgress{
     2 public:
     3     virtual void DoProgress(float value)=0;
     4     virtual ~IProgress(){}
     5 };
     6 
     7 
     8 class FileSplitter
     9 {
    10     string m_filePath;
    11     int m_fileNumber;
    12 
    13     List<IProgress*>  m_iprogressList; // 抽象通知机制,支持多个观察者
    14     
    15 public:
    16     FileSplitter(const string& filePath, int fileNumber) :
    17         m_filePath(filePath), 
    18         m_fileNumber(fileNumber){
    19 
    20     }
    21 
    22 
    23     void split(){
    24 
    25         //1.读取大文件
    26 
    27         //2.分批次向小文件中写入
    28         for (int i = 0; i < m_fileNumber; i++){
    29             //...
    30 
    31             float progressValue = m_fileNumber;  //
    32             progressValue = (i + 1) / progressValue;
    33             onProgress(progressValue);//发送通知
    34         }
    35 
    36     }
    37 
    38 
    39     void addIProgress(IProgress* iprogress){
    40         m_iprogressList.push_back(iprogress);
    41     }
    42 
    43     void removeIProgress(IProgress* iprogress){
    44         m_iprogressList.remove(iprogress);
    45     }
    46 
    47 
    48 protected:
    49     virtual void onProgress(float value){
    50         
    51         List<IProgress*>::iterator itor=m_iprogressList.begin();
    52 
    53         while (itor != m_iprogressList.end() )
    54             (*itor)->DoProgress(value); //更新进度条
    55             itor++;
    56         }
    57     }
    58 };
    FileSplitter2.cpp
     1 //观察者——控件窗口
     2 class MainForm : public Form, public IProgress
     3 {
     4     TextBox* txtFilePath;
     5     TextBox* txtFileNumber;
     6 
     7     ProgressBar* progressBar;
     8 
     9 public:
    10     void Button1_Click(){
    11 
    12         string filePath = txtFilePath->getText();
    13         int number = atoi(txtFileNumber->getText().c_str());
    14 
    15         ConsoleNotifier cn;
    16 
    17         FileSplitter splitter(filePath, number);
    18 
    19         splitter.addIProgress(this); //订阅通知
    20         splitter.addIProgress(&cn); //订阅通知
    21 
    22         splitter.split();
    23 
    24         splitter.removeIProgress(this);
    25 
    26     }
    27 
    28     virtual void DoProgress(float value){
    29         progressBar->setValue(value);
    30     }
    31 };
    32 
    33 //观察者——控制台
    34 class ConsoleNotifier : public IProgress {
    35 public:
    36     virtual void DoProgress(float value){
    37         cout << ".";
    38     }
    39 };
    MainForm2.cpp

    实现细节应依赖于抽象。

    progressBar是一个通知控件(实现细节), 本质为通知,故新建一个IProgress类表达一个抽象的通知机制。

    让观察者MainForm继承IProgress,

    不推荐多继承,但有一种情况: 继承了一个主的继承类,其他都是接口类或抽象基类。

    在MainForm中,实现DoProgress方法,即具体细节。FileSpliter不再像之前的代码依赖于界面类,与之解耦合。

    模式定义

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

     类图

    Observer -- IProgress     

      Update() -- doProgress

    Subject (可将以下三方法放在基类Subject中实现,让FileSpliter继承此基类)

      Attach(Observer )  -- addIProgress()  

      Detach(Observer ) -- removeIProgress()

      Notify()  --  onProgress()

    ConcreteSubject -- FileSpliter(主体对象)

    ConcreteObserver --  具体的观察者(ConsoleNotifier /  MainForm)

    要点总结

    • 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
    • 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
    • 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
    • Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
  • 相关阅读:
    DM8168 新三板系统启动
    AndroidAndroid程序提示和消息button响应事件
    Linux核心regulator建筑和准备
    对话(VC_Win32)
    VOJ 1067 Warcraft III 守望者的烦恼 (矩阵高速功率+dp)
    有所述网络流的上限和下限
    Matlab Newton‘s method
    Cocos2d-Java安装和配置跨平台游戏引擎以及相关的开发工具
    n每个计数的概率和发生骰子--动态规划
    自己写CPU第九阶段(3)——加载存储指令说明2(swl、swr)
  • 原文地址:https://www.cnblogs.com/y4247464/p/14221408.html
Copyright © 2020-2023  润新知