C++ Observer设计模式的学习

206 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

原文链接:blog.csdn.net/roufoo/arti…

本周学习了几种设计模式,包括Template, Strategy, Observer, Decorator和Bridge,觉得都有用。本文重点回顾一下Observer设计模式。

Observer设计模式主要应用在当目标对象的状态发生改变的时候,所有的依赖对象(也就是观察者对象)都将得到通知并自动更新。说到"通知",有嵌入式背景的同学可能马上就会想到用polling和interrupt这两种方法,但我们这里说的设计模式是在应用层,跟底层的OS和hardware无关,其实完全是两个不同领域的东西。

这里要注意的是,目标对象发送通知时,无需指定观察者,通知会自动传播给已经订阅通知的观察者。目标对象并不知道谁是观察者。我们想象这样一个例子,假设有几百万个客户都订阅了一个微信公众号,这个公众号每天都发送一些八卦新闻。客户可以自己选择订阅或退订,公众号的任务就是发送新闻而已,所有的客户在它看来都一样,没有区别。它一发送新闻,那些客户就收到新闻了。这个例子就是一个观察者模式的实际运用。

观察者模式的结构描述如下图:

这里Subject和Observer之间是松耦合。Subject的Notify()里面会调用一个loop,遍历所有的观察者并调用它们的update()函数。在Subject看来所有的观察者都是一样的,它们的update()没有区别。Observer通过订阅/退订,可以决定要不要收到通知。

Observer设计模式在基于时间的UI框架中用的很多。一个具体例子如下:

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++;
		}
	}
};

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->setValue(value);
	}
};

class ConsoleNotifier : public IProgress {
public:
	virtual void DoProgress(float value){
		cout << ".";
	}
};

在这个例子中,FileSplitter是ConcreteSubject, MainForm和ConsoleNotifier都是客户(ConcreteObserver)。FileSplitter切割文件的时候,会调用onProgress()函数。onProgress()就相当于上面图里面的Notify(),里面会遍历m_iprogressList,这个就是客户列表。客户通过splitter.addIProgress()来订阅通知。

另外要注意的是,如果订阅的客户多了,Subject里面的Notify()就会比较慢,变成系统的瓶颈。看来这个Observer模式也是有局限性的。

下面是一个完整的observer模式的例子。代码参考自 blog.csdn.net/wuzhekai198…

#include <string>
#include <iostream>
using namespace std;
#include <list>

//pure virtual class, like JAVA's interface
class Observer  
{
public:
	Observer() {}
	virtual ~Observer() {}
	virtual void Update() {} 
};

class Blog  
{
public:
	Blog() {}
	virtual ~Blog() {}
	void Attach(Observer *observer) { m_observers.push_back(observer); }
	void Remove(Observer *observer) { m_observers.remove(observer); }
	void Notify()
	{
		list<Observer*>::iterator iter = m_observers.begin();
		for(; iter != m_observers.end(); iter++)
			(*iter)->Update();
	}
	virtual void SetStatus(string s) { m_status = s; }
	virtual string GetStatus() { return m_status; }
private:
	list<Observer* > m_observers;
protected:
	string m_status;
};


class BlogCSDN : public Blog
{
private:
	string m_name;
public:
	BlogCSDN(string name): m_name(name) {}
	~BlogCSDN() {}
	void SetStatus(string s) { m_status = "CSDN Notice : " + m_name + s; }
	string GetStatus() { return m_status; }
};

class ObserverBlog : public Observer   
{
private:
	string m_name;
	Blog *m_blog;
public: 
	ObserverBlog(string name,Blog *blog): m_name(name), m_blog(blog) {}
	~ObserverBlog() {}
	void Update()
	{ 
		string status = m_blog->GetStatus();
		cout<<m_name<<"-------"<<status<<endl;
	}
};

int main()
{
	Blog *blog = new BlogCSDN("roufoo");
	Observer *observer1 = new ObserverBlog("Tom", blog);
	Observer *observer2 = new ObserverBlog("Jerry", blog);
	blog->Attach(observer1);
	blog->Attach(observer2);
	blog->SetStatus("roufoo's new post published"");
	blog->Notify();
	delete blog; 
	delete observer1;
	delete observer2;
	return 0;
}