觀察者模式 (Observer pattern) 3

上一篇我們推導出需要 Observer 抽象基礎類別的原因,本篇我們來探討需要 Subject 抽象基礎類別的原因。

Observer pattern

主題通知觀察者資料更新有兩種方式,一種是直接將資料推 (push) 送給觀察者;另一種是先通知觀察者,等觀察者有時間再回主題拉 (pull) 資料。

如果是將資料推 (push) 送給觀察者,Update 需要修改為傳遞資料參數,如下:

class IObserver
{
public:
	virtual ~IObserver() = 0
	{}

public:
	virtual void Update(double dPrice) = 0;
};

// 技術分析
class CTAViewObserver : public IObserver
{
public:
	virtual void Update(double dPrice) override
	{}
};

開發看盤軟體時,單一頁面觀察者不會只顯示一檔股票, 如果我們的觀察者對多個股票主題有興趣會怎麼樣呢?假設現在關注的主題有兩個,台積電與鴻海。

class CTSMCStockSubject final // 台積電
{
public:
	void Subscribe(const std::shared_ptr<IObserver>& ob)
	{
		m_observers.emplace(ob);
	}

	void Unsubscribe(const std::shared_ptr<IObserver>& ob)
	{
		m_observers.erase(ob);
	}

	void Notify()
	{
		for (auto& ob : m_observers)
		{
			double dPrice = 300;
			ob->Update(dPrice);
		}
	}

private:
	std::set<std::shared_ptr<IObserver>> m_observers;
};

class CFoxconnStockSubject final // 鴻海
{
public:
	void Notify()
	{
		for (auto& ob : m_observers)
		{
			double dPrice = 70;
			ob->Update(dPrice);
		}
	}

	// 其他程式碼省略...
};

注意 Notify 內呼叫的 Update 並沒有傳識別參數,那麼觀察者又怎麼知道是那個主題通知它呢?因此我們可能會修改為多傳一個股票代碼 (sStockID) 給 Update。

class IObserver
{
public:
	virtual ~IObserver() = 0
	{}

public:
	virtual void Update(const std::string& stockID, double dPrice) = 0;
};

// 技術分析
class CTAViewObserver : public IObserver
{
public:
	virtual void Update(const std::string& stockID, double dPrice) override
	{}
};

那如果是先通知,等有時間再回主題拉 (pull) 資料呢?勢必 Update 就要傳入不同主題類別的指標,因此引進 Subject 抽象基礎類別就有必要。

class ISubject
{
public:
	virtual ~ISubject() = 0
	{}

public:
	virtual void Subscribe(const std::shared_ptr<IObserver>& ob) = 0;
	virtual void Unsubscribe(const std::shared_ptr<IObserver>& ob) = 0;
	virtual void Notify() = 0;

public:
	virtual std::string GetStockID() const = 0;
	virtual double GetPrice() const = 0;
};

// 台積電
class CTSMCStockSubject : public ISubject
{
	// 其他程式碼省略...

public:
	virtual void Notify() override
	{
		for (auto& ob : m_observers)
		{
			ob->Update(this);
		}
	}

public:
	virtual std::string GetStockID() const override
	{
		return "2330.TW";
	}

	virtual double GetPrice() const override
	{
		return 300;
	}

private:
	std::set<std::shared_ptr<IObserver>> m_observers;
};

// 鴻海
class CFoxconnStockSubject : public ISubject
{
	// 其他程式碼省略...

public:
	virtual std::string GetStockID() const override
	{
		return "2317.TW";
	}

	virtual double GetPrice() const override
	{
		return 70;
	}
};

Update 改為接收 ISubject*,其他參數都可以省略。

class IObserver
{
public:
	virtual ~IObserver() = 0
	{}

public:
	virtual void Update(ISubject* pSubject) = 0;
};

// 技術分析
class CTAViewObserver : public IObserver
{
public:
	virtual void Update(ISubject* pSubject) override
	{
		// 這裡可以先將 pSubject 儲存,稍後有時間再回主題類別拉 (pull) 資料
		m_mapStockID2Subject[pSubject->GetStockID()] = pSubject;
	}

private:
	std::map<std::string, ISubject*> m_mapStockID2Subject;
};

到此為止說明了需要 Subject 抽象基礎類別的原因,下一篇會對設計做更深入的探討。

發佈留言