【组件协作】 观察者模式

观察者模式

观察者模式仍然是一个组件协作式的设计模式, 目的在于解决框架的协作问题, 我们需要为某些对象建立
一种“通知依赖关系”, 也就是说, 当一个对象(目标对象)的状态发生改变的时候, 所有的依赖对象(观察者对象)
都将得到通知。 如果这也的依赖关系过于紧密, 则过度耦合

代码

假设现在我们需要一个文件分割器, 我们需要一个界面和一个提供实际功能的类
原始状态
MainForm.cpp

class MainForm : public Form
{
    TextBox* txtFilePath_;
    TextBot* txtFileNumber_;
public:
    void BtnClick()
    {
        string filePath = txtFilePath_->getText();
        int number = atoi(txtFileNumber_->getText().c_str());

        FileSplitter splitter(filePath, number);
        splitter.Split();
    }
};

FileSplitter.cpp

class FileSplitter
{
    string filePath_;
    int fileNumber_;
public:
    FileSplitter(const string& filePath, int fileNumber) : 
        filePath_(filePath), fileNumber_(fileNumber) {}
    void split()
    {
        // I

        // O
        for (size_t i = 0; i < fileNumber_; i ++ )
        {
            // code...
        }
    }
};

现在有个需求:假设文件巨无敌大, 那么分割会很慢, 现在需要一个进度条进行展示, 不然用户看不到进度会很焦虑
也就是说, MainForm要添加一个类似于ProgressBar的类作为成员变量, 用来展示分割进度

MainForm.cpp

class MainForm : public Form
{
    TextBox* txtFilePath_;
    TextBot* txtFileNumber_;
    ProgressBar* progressBar_;
public:
    void BtnClick()
    {
        string filePath = txtFilePath_->getText();
        int number = atoi(txtFileNumber_->getText().c_str());

        FileSplitter splitter(filePath, number, progressBar_);
        splitter.Split();
    }
};

FileSplitter.cpp


class FileSplitter
{
    string filePath_;
    int fileNumber_;
    ProgressBar* progressBar_;
public:
    FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) : 
        filePath_(filePath), fileNumber_(fileNumber), progressBar_(progressBar) {}
    void split()
    {
        // I

        // O
        for (size_t i = 0; i < fileNumber_; i ++ )
        {
            // code...

            if (progressBar_)
            {
                progressBar_->setValue(i * 100 / fileNumber_);
            }
        }
    }
};

优化

上面写法很明显违背了依赖倒置原则, 高层模块不应该依赖底层, 二者都应该依赖抽象, 抽象不应该依赖细节,细节应该依赖抽象

什么是依赖?
A依赖B, 意思是A编译的时候, B需要存在, 这个时候才能编译通过
FileSplitter依赖ProgressBar, 那么FileSplitter编译的时候,ProgressBar需要存在, 才能编译通过
但是, 进度条的实现只有ProgressBar这么一种吗?
显然不是

为什么不应该依赖细节?因为细节非常容易变化
怎么重构呢?
依赖倒置原则给我们提供了方案, 我们应该依赖抽象, 假如ProgressBar有一个父类:ControlBase, 那么FileSplitter就依赖ControlBase
但如果是这样的话,
FileSplitter类里的for循环怎么处理?
父类一定会有这个抽象函数吗?
所以, 单纯的找父类是一个很粗浅的认识, 要明白, 咱们的ProgressBar在系统中的角色
显然:ProgressBar是一个通知, 而不是控件, 通知才是这个组件的根本属性
而控件太detail了

Interface: IProgressBar
IProgressBar.cpp

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

ProgressBar是一个具体的通知控件, 而IProgressBar是一个抽象的通知机制
此时就可以把FileSplitter的ProgressBar更换成IprogressBar了
然后去调用DoProgress函数


至于MainForm类的话, 使用多继承
继承Form和IProgressBar, 并且实现IProgressBar的DoProgress函数

MainForm.cpp

virtual void MainForm::DoProgress(float value)
{
    progressBar_->setValue(value);  
}

重构优化到这里, 已经非常松耦合了, 并且遵从依赖倒置原则
但是···如果要支持多个通知呢?也就是说, 要支持多个观察者, 怎么做?

ConsoleNotifiler.cpp

class ConsoleNotifier : public IProgress
{
public:
    virtual void MainForm::DoProgress(float value)
    {
        progressBar_->setValue(value);  
        cout << ".";
    }
};

第一处更改的地方, 就是将IprogressBar换成std::vector<IProgress*>, 当然, 也可以更换成List
第二处修改时增加了一个增加/删除/遍历成员的接口
ConsoleNotifiler.cpp

void addItem(IProgress* item)
{
    iprogressVec.push_back(item);
}

void removeItem(IProgress* item)
{
    iprogressVec.remove(item);
}

// 遍历
void forEach()
{
    // code...
}

// 返回迭代器
void Begin()
{
    // code...
}

void End()
{
    // code...
}

然后只需要遍历每个Iprogress, 调用DoProgress函数即可

总结

观察者模式是一种组件间协作的设置模式, 用于一对多的通知机制
目标发送通知的时候, 无需指定观察者,通知(可以携带通知信息作为参数)会自动传播(调用DoProgress函数)

观察者自己确定是否需要订阅通知(订阅通知就是上面的add),目标对象对此一无所知

观察者模式常常用于基于事件的UI框架中, 也是MVC模式的一个重要组成成分

特别的, 调用DoProgress函数并不是通知观察者, 而是执行通知机制, 只是说执行结果为:通知所有的观察者

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值