1. 程式人生 > 其它 >設計模式--Note2--元件協作類

設計模式--Note2--元件協作類

Template Method

定義一個操作中的演算法的骨架(穩定),而將一些步驟的實現延遲(變化)到子類中。Template Method使得子類可以不改變(複用)一個演算法的結構即可重定義(override重寫)該演算法的某些特定步驟。

早繫結與晚繫結

區分穩定與變化

要點總結

  1. 非常基礎的設計模式

  2. 最簡潔的機制(虛擬函式的多型性)

  3. 提供擴充套件點(繼承+多型)

  4. 反向控制結構(Lib控制App),Lib呼叫App

    上述例項:

    1. run()是固定的
    2. 執行時,Lib的run()呼叫App的Step2、Step4
  5. 具體實現中,被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

策略

定義一系列演算法,把它們一個個封裝起來,並且使它們可以相互替換(變化)。該模式使得演算法可以獨立於使用它的客戶程式(穩定)而變化(擴充套件、子類化)。

解決什麼問題

在軟體構建過程中,某些物件使用的演算法可能多種多樣,經常改變,如果將這些演算法都編碼到物件中,會使得物件變得異常複雜,而且有時候支援不使用的演算法也是一個性能負擔。

如何在執行時根據需求更透明的更改物件的演算法?

將演算法與物件本身解耦,從而避免上述問題?

結構

要點總結

  1. Strategy及其子類為元件提供了一系列可重用的演算法,從而使得型別在執行時方便的根據需要在各個演算法之間切換
  2. Strategy提供了判斷語句外的另一種選擇
  3. 如果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

觀察者模式

定義物件間的一種一對多(變化)的依賴關係,以便當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並自動更新。

解決什麼問題

在軟體構件過程中,我們需要為某些物件建立一種”通知依賴關係“,一個物件(目標物件)的狀態傳送改變,所有的依賴物件(觀察者物件)都將得到通知。如果這樣的依賴關係過於親密,將使軟體不能很好地抵禦變化。

使用面向物件技術,可以將這種依賴關係弱化,並形成一種穩定的依賴關係。從而實現軟體體系結構的鬆耦合。

結構

要點總結

  1. 使用面向物件的抽象,Observer模式使得我們可以獨立地改變目標與觀察者,從而使二者之間的依賴關係達致鬆耦合。
  2. 目標傳送通知時,無需指定觀察者,通知(可以攜帶通知資訊作為引數)會自動傳播。
  3. 觀察者自己決定是否需要訂閱通知,目標物件對此一無所知。
  4. 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;
};
轉載請註明出處