設計模式--Note2--元件協作類
阿新 • • 發佈:2021-09-08
Template Method
定義一個操作中的演算法的骨架(穩定),而將一些步驟的實現延遲(變化)到子類中。Template Method使得子類可以不改變(複用)一個演算法的結構即可重定義(override重寫)該演算法的某些特定步驟。
早繫結與晚繫結
區分穩定與變化
要點總結
-
非常基礎的設計模式
-
最簡潔的機制(虛擬函式的多型性)
-
提供擴充套件點(繼承+多型)
-
反向控制結構(Lib控制App),Lib呼叫App
上述例項:
- run()是固定的
- 執行時,Lib的run()呼叫App的Step2、Step4
-
具體實現中,被Template Method呼叫的方法可以有也可以沒有具體實現,一般推薦設定為protected方法
示例
// 結構化 class Library { public: Step1(); Step3(); Step5(); ... }; class Application { public: Step2(); Step4(); }; int main() { Library lib; Application app; lib.Step1(); if (app.Step2()) { lib.Step3(); } for (...) { app.Step4(); } lib.Step5(); ... }
// 面向物件 class Library { public: // 穩定中包含變化 void Run() { Step1(); if (Step2()) { // 支援變化 虛擬函式多型呼叫 Step3(); } for (...) { Step4();// 支援變化 虛擬函式多型呼叫 } Step5(); } virtual ~Library(); private: Step1(); Step3(); Step5(); virtual Step2(); virtual Step4(); }; class Application : public Library { public: Step4(); Step5(); }; int main() { Library* pLib = new Application; pLib->run();// run()並不是虛擬函式,此處呼叫基類的run(),但是在run()內部的Step2()、Step4()又是虛擬函式,呼叫的時Application的Step2()、Step4() delete pLib; ... }
Strategy
策略
定義一系列演算法,把它們一個個封裝起來,並且使它們可以相互替換(變化)。該模式使得演算法可以獨立於使用它的客戶程式(穩定)而變化(擴充套件、子類化)。
解決什麼問題
在軟體構建過程中,某些物件使用的演算法可能多種多樣,經常改變,如果將這些演算法都編碼到物件中,會使得物件變得異常複雜,而且有時候支援不使用的演算法也是一個性能負擔。
如何在執行時根據需求更透明的更改物件的演算法?
將演算法與物件本身解耦,從而避免上述問題?
結構
要點總結
- Strategy及其子類為元件提供了一系列可重用的演算法,從而使得型別在執行時方便的根據需要在各個演算法之間切換
- Strategy提供了判斷語句外的另一種選擇
- 如果Strategy物件沒有例項變數,那麼各個上下文可以共享同一個Strategy物件,從而節省開銷
示例:稅率問題
// 結構化
enum TaxBase { // 變化
CN_Tax,
US_Tax,
DE_Tax,
FR_Tax // 增加需求
};
class SalesOrder {
public:
// 穩定
double CalculateTax() {
// ...
if (tax == CN_Tax) {
// ...
}
else if (tax == US_Tax) {
}
else if (tax == DE_Tax) {
}
else if (tax == FR_Tax) { // 增加 應該對擴充套件開放,對修改封閉
}
//...
}
private:
TaxBase tax;
};
// Strategy
class TaxStrategy {
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}// 基類最好都實現一個虛的解構函式
};
// 變化
class CNTax : public TaxStratygy {
public:
virtual double Calculate(const Context& context) {
// ...
}
};
class USTax : public TaxStratygy {
public:
virtual double Calculate(const Context& context) {
// ...
}
};
class DETax : public TaxStratygy {
public:
virtual double Calculate(const Context& context) {
// ...
}
};
// 增加
class FRTax : public TaxStratygy {
public:
virtual double Calculate(const Context& context) {
// ...
}
};
// 穩定
class SalesOrder {
public:
SalesOrder(StrategyFactory* strategyFactory) {
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder() {
delete this->strategy;
}
double Calculate() {
// ...
Context context();
double val = strategy->Calculate(contex);
// ...
}
private:
TaxStrategy* strategy;
};
Observer
觀察者模式
定義物件間的一種一對多(變化)的依賴關係,以便當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並自動更新。
解決什麼問題
在軟體構件過程中,我們需要為某些物件建立一種”通知依賴關係“,一個物件(目標物件)的狀態傳送改變,所有的依賴物件(觀察者物件)都將得到通知。如果這樣的依賴關係過於親密,將使軟體不能很好地抵禦變化。
使用面向物件技術,可以將這種依賴關係弱化,並形成一種穩定的依賴關係。從而實現軟體體系結構的鬆耦合。
結構
要點總結
- 使用面向物件的抽象,Observer模式使得我們可以獨立地改變目標與觀察者,從而使二者之間的依賴關係達致鬆耦合。
- 目標傳送通知時,無需指定觀察者,通知(可以攜帶通知資訊作為引數)會自動傳播。
- 觀察者自己決定是否需要訂閱通知,目標物件對此一無所知。
- Observer模式是基於事件的UI框架中非常常用的設計模式,也是MVC模式的一個重要組成部分。
示例:視窗訊息通知
// 最初實現
class FileSplitter {
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) {
}
}
private:
string m_filePath;
int m_fileNumber;
};
class MainForm : public Form {
public:
void Button1_Click() {
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number);
splitter.split();
}
private:
TextBox* txtFilePath;
TextBox* txtFileNumber;
};
// 增加一個通知功能 顯示檔案切分進度
class FileSplitter {
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) {
// ...
if (m_progressBar != nullptr) {
float progressValue = m_fileNumber;
m_progressBar->setValur((i + 1) / progressValue);
}
}
}
private:
string m_filePath;
int m_fileNumber;
ProgressBar* m_progressBar;// 通知的方式是變化的 當通知是穩定的 穩定不應該依賴變化,而應該依賴抽象
};
class MainForm : public Form {
public:
void Button1_Click() {
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, progressBar);
splitter.split();
}
private:
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
};
// 通知時,除了ProgressBar外,可能會使用其他方式
// 間ProgressBar與FileSplitter解耦合
class IProgress {
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress() {}
};
class ProgressBar {
public:
void setValue(float value) {
// ...
}
}
class FileSplitter {
public:
FileSplitter(const string& filePath, int fileNumber, IProgress* iprogress) :
m_filePath(filePath),
m_fileNumber(fileNumber),
m_progressBar(iprogress) {
}
void split() {
// 1.讀取檔案
// 2.分批向小檔案寫入
for (int i = 0; i < m_fileNumber; ++i) {
// ...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);
}
}
protected:
void onProgress(float value) {
if (m_iprogress != nullptr) {
m_iprogress->DoProgress(value);
}
}
private:
string m_filePath;
int m_fileNumber;
//ProgressBar* m_progressBar;// 具體通知控制元件
IProgress* m_iprogress; // 抽象通知
};
class MainForm : public Form, public IProgress {
public:
void Button1_Click() {
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, this);
splitter.split();
}
virtual void DoProgress(float value) {
progressBar->setValue(value);
}
private:
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
};
// 支援出現變化時通知多個物件
class IProgress {
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress() {}
};
class ProgressBar {
public:
void setValue(float value) {
// ...
}
}
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value) {
// ...
}
};
class FileSplitter {
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:
void onProgress(float value) {
for (auto iprogress : m_iprogressList) {
m_iprogress->DoProgress(value);
}
}
private:
string m_filePath;
int m_fileNumber;
//ProgressBar* m_progressBar;// 具體通知控制元件
//IProgress* m_iprogress; // 抽象通知
vector<IProgress*> m_iprogressList; // 支援多個觀察者
};
class MainForm : public Form, public IProgress {
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();
}
virtual void DoProgress(float value) {
progressBar->setValue(value);
}
private:
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
};
轉載請註明出處