C++设计模式3——观察者(Observer)模式

1. 观察者(Observer)模式介绍

定义
  定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都
得到通知并自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。
模式的结构
观察者模式的主要角色如下。

  1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

类图

红色部分为稳定,蓝色为非稳定部分
在这里插入图片描述


在这里插入图片描述


2. 为了方便理解,这里举一个例子

2.1 例子一

  对于一个实现文件切割的类,如果需要对其增加一个显示进度条的 选项,使得用户可以知道文件切割的进度。如果直接在类中增加一个字段来表示进度条,这种修改方式好不好?
代码(伪代码)如下:
mainform.cpp

//一个实现文件分割器是类
class MainForm : public Form
{
	//文件路径
	TextBox* txtFilePath;
	//用户希望分隔的文件个数
	TextBox* txtFileNumber;
	//进度条
	ProgressBar* progressBar;
 
public:
	//点击按钮,我们会收集文件信息,然后去调用filespliter
	void Button1_Click(){
 
		string filePath = txtFilePath->getText();
		int number = atoi(txtFileNumber->getText().c_str());
 
		FileSplitter splitter(filePath, number, progressBar);
 
		splitter.split();
 
	}
};

对应的实现代码如下:
FileSplitter.cpp

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++){
			//...
			float progressValue = m_fileNumber;
			progressValue = (i + 1) / progressValue;
			//更新进度条的进展
			m_progressBar->setValue(progressValue);
		}
 
	}
};

  直接在类里面添加字段,这样是不合适的,违背了八大设计原则。如果以后的需求还有变更,还继续对类进行修改吗?这样是不对的。

  依赖倒置原则给我们的一个说法——不要去依赖A,而是我依赖A的抽象基类。

  但是单纯的按照找父类的方式去寻找,你会发现走进了死胡同。因为fliesplitter.cpp文件实现进度条绘制的函数setValue()ProgressBar的父类中并不存在。因此,单纯找基类是一个很粗浅的认识,

  仔细分析你会发现,ProgressBar在类中扮演的角色是依赖通知。

  对于通知,其实我们可以用相对抽象的方式来表达通知,而不是具体控件来表达通知。(下面代码中添加了一个IProgress这样一抽象接口类,来解开耦合性)

根据依赖倒置原则修改后的代码:
mainform.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());
 
		ConsoleNotifier cn;
 
		FileSplitter splitter(filePath, number);
 
		splitter.addIProgress(this); //订阅通知
		splitter.addIProgress(&cn)//订阅通知
 
		splitter.split();
 
		splitter.removeIProgress(this);
 
	}
 
	virtual void DoProgress(float value){
		//这里可以实现progressBar,因为mainForm和progressBar本身就是一体的
		progressBar->setValue(value);
	}
};
 
class ConsoleNotifier : public IProgress {
public:
	virtual void DoProgress(float value){
		cout << ".";
	}
};

FileSplitter.cpp

class IProgress{
public:
	virtual void DoProgress(float value)=0;
	virtual ~IProgress(){}
};
 
 
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++;
		}
	}
};

2.2 例子二(比较好理解)

老师没有来,学生吵吵闹闹,老师一来学生立马安静:
完整代码:

#include<iostream>
#include<list>
using namespace std;
//抽象的学生接口,抽象的观察者
class AbstractStudent{
public:
    virtual void Update()=0;
};

//具体的学生,具体的观察者
class StudentA : public AbstractStudent{
public:
    StudentA(){
        cout<<"学生A正在吵闹..."<<endl;
    }
    virtual void Update(){
        cout<<"学生A停止吵闹,立即坐正!"<<endl;
    }
};

class StudentB : public AbstractStudent{
public:
    StudentB(){
        cout<<"学生B正在吵闹..."<<endl;
    }
    virtual void Update(){
        cout<<"学生B停止吵闹,立即坐正!"<<endl;
    }
};

class StudentC : public AbstractStudent{
public:
    StudentC(){
        cout<<"学生C正在吵闹..."<<endl;
    }
    virtual void Update(){
        cout<<"学生C停止吵闹,立即坐正!"<<endl;
    }
};

//抽象观察目标
class AbstractTeacher{
public:
    //添加观察者
    virtual void addStudent(AbstractStudent* hero)=0;
    //删除观察者
    virtual void deleteStudent(AbstractStudent* hero)=0;
    //通知所有观察者
    virtual void notify()=0;
};

//具体的观察目标
class TeacherA : public AbstractTeacher{
public:
    virtual void addStudent(AbstractStudent* student){
        PStudentList.push_back(student);
    }

    virtual void deleteStudent(AbstractStudent* student){
        PStudentList.remove(student);
    }

    virtual void notify(){
        for(list<AbstractStudent*>::iterator it = PStudentList.begin();it != PStudentList.end();it++){
            (*it)->Update();
        }
    }
private:
    list<AbstractStudent*> PStudentList;
};

void test1(){
    //创建观察者
    AbstractStudent* studentA = new StudentA;
    AbstractStudent* studentB = new StudentB;
    AbstractStudent* studentC = new StudentC;
    //创建观察目标
    AbstractTeacher* teacherA = new TeacherA;
    teacherA->addStudent(studentA);
    teacherA->addStudent(studentB);
    teacherA->addStudent(studentC);

    cout<<"studentB去上厕所了..."<<endl;
    teacherA->deleteStudent(studentB);

    cout<<"teacherA老师来了!!"<<endl;
    teacherA->notify();
}

int main(){
    test1();
    return 0;
}

运行结果:
在这里插入图片描述

3. 观察者(Observer)模式优缺点

它的主要优点如下:

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  2. 目标与观察者之间建立了一套触发机制。

它的主要缺点如下。

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值