電子商務系統裏做訂單的計算,訂單計算裏除了金額交易,還有很重要的一項是稅的計算。假如支持跨國的結算,就要考慮到不同國家的稅的算法不同。比如中國、美國、德國稅法相差非常大,所以在算法實現層面就要支持非常靈活的計算稅的方法。最簡單直觀容易想到的做法是什麼呢?
用枚舉enum類型來支持中國、美國、德國三種國家的稅法計算
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax
};
class SalesOrder {
TaxBase tax;
public:
double CalculateTax() {
//...
if(tax == CN_Tax){
// CN...TaxRules Impl
} else if(tax == US_Tax){
// US...TaxRules Impl
} else if(tax == DE_Tax) {
// DE...TaxRules Impl
}
// ....
}
}
用if else 或switch case來實現,功能正確也無可厚非,
但是作爲面向對象設計,要對設計模式有一個思維層次,不能靜態去看一個軟件的設計,而是要動態的看。
作爲程序員要有時間軸的概念,很多領域的問題或需求問題,靜態的看問題是暴露不出來,也不需要重構,但加上一個時間軸之後,角度就不同了。
加上時間軸,考慮到問題未來的變化,即功能擴展,比如未來支持韓國、日本的稅法,這是很有可能出現的業務變化情況。你的客戶或產品經理,可能每天提10個需求,比如支持法國稅法,修改方式就是:
enum TaxBase {
...
FR_Tax, // 更改
};
class SalesOrder {
...
else if(tax==FR_Tax){ // 更改
// FR...TaxRules Impl
}
}
注意這裏的更改,之前講過一個設計原則,開閉原則:對擴展開放,對修改關閉。
類模塊應儘可能用擴展的方式支持未來的變化,而不是直接修改源代碼來應對變化。
上面兩處更改,帶來很大的軟件複用上的負擔,需要重新更改,編譯,測試,部署。一堆代價就出來了。儘可能用擴展的方式。
Strategy 模式來解決這個問題
新的實現方式有什麼好處呢? 設計模式中比較好處,就要放到時間軸裏面。
class TaxStrategy{
public:
virtual double Calculate(const Context& context) = 0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context) {
// CN...TaxRules Impl
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context) {
//US...TaxRules Impl
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context) {
//DE...TaxRules Impl
}
};
class FRTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context) {
//FR...TaxRules Impl
}
};
class SalesOrder {
private:
TaxStrategy* strategy;
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
Context context();
double val = strategy->Calculate(context); //多態調用
// ...
}
};
只需要新創建一個cpp file中添加新的FR Tax的class,這是一個擴展。可以通過增量編譯或外部lib庫的方式生效。而SalesOrder是不需要改動的,得到了複用性。
這就遵循了開放封閉原則。
面向對象、設計模式當中講的複用性,是指的編譯單位,二進制文件層面的複用性,注意與源代碼片段級別的複用進行區分。
有經驗的朋友應該知道,在一段代碼塊後去補充一段代碼,這種行爲是很危險的,邏輯正確性有時候很難再修改前後得到保證。經常會是該片段引入bug
定義一系列算法,把他們一個個封裝起來,並且使他們可互相替換(變化)。該模式使得算法可獨立於使用它的客戶程序(穩定)而變化(擴展,子類化)。
左邊的context就是salesorder,
Strategy對應TaxStrategy
紅色是穩定的,藍色是變化的部分。
要點總結
Strategy及其子類爲組件提供了一系列可重用的算法,從而可以使得類型在運行時方便地根據需要在各個算法之間進行切換。
Strategy模式提供了用條件判斷語句意外的另一種選擇,消除條件判斷語句,就是在解耦合。含有許多條件判斷語句的代碼通常都需要Strategy模式
如果Strategy對象沒有實例變量,那麼各個上下文可以共享同一個strategy對象,從而節省對象開銷。
如果Strategy對象沒有實例變量,那麼各個上下文可以共享同一個strategy對象,從而節省對象開銷。