想像一下:
隨著公司的電商系統生意越做越大,支援的付款方式越來越多,負責處理訂單的OrderManager行數也越來越多,程式碼充滿了層層疊疊的if-else,老闆上周開會時又提出要新增其他付款方式,眼看程式碼就要爆炸,有沒有什麼可以簡化程式的方法?
準備重構
(NOTE: 本篇範例程式碼皆為PHP)
我們先來看看原本的程式:
class OrderManager
{
public function pay($payWay)
{
// ... 處理其他事情
if ($payWay == 'CreditCard') {
$payResult = $this->payByCreditCard();
} else if ($payWay == 'atm') {
$payResult = $this->payByAtm();
} else if ($payWay == 'linePay') {
$payResult = $this->payByLinePay();
}
// ... 處理其他事情
}
public function payByCreditCard()
{
// 處理信用卡付款....
}
public function payByAtm()
{
// 處理ATM付款....
}
public function payByLinePay()
{
// 處理LinePay付款....
}
}
隨著公司業務的發展,付款方式越來越多,payByCreditCard
、payByAtm
等處理付款的程式,一隻一隻的增加,原本看起來結構還不錯的OrderManager
也越來越長,越來越複雜。
於是,再新增新的付款方式之前,是重構的時候了。
仔細觀察後,我們發現,付款的流程其實都差不多。不論是信用卡、ATM或是LinePay付款,都可以歸納成兩步驟,付款
跟取得付款結果
。
根據這個結論,我們可以建立一個Interface
:
interface PayMethodInterface
{
// 付款
public function pay($orderInfo);
// 取得付款結果
public function getResult();
}
接著我們將原本放在OrderManager
中的付款程式碼,移到另一個實作PayMethodInterFace
的類別中,以信用卡(payByCreditCard)為例:
class CreditCard implements PayMethodInterface
{
// 付款
public function pay($orderInfo)
{
// 呼叫api執行付款...
}
// 取得付款結果
public function getResult()
{
// 回傳付款結果
}
}
重構
將付款程式都搬移之後,我們就可以開始改寫原本的OrderManager
了。
要做的事情很簡單,就是將原本的payByXXX
都移除掉,並修改pay method,這個pay需要傳入一個實作PayMethodInterface
介面的物件:
class OrderManager
{
public function pay(PayMethodInterface $payMethod)
{
$payMethod->pay($orderInfo);
return $payMethod->getResult();
}
}
這樣的意思是,以後的付款行為,都會藉由實作PayMethodInterface
介面的物件來處理,如果今天傳入pay的是CreditCard
物件,那麼這段程式就會自動去使用CreditCard
物件來做信用卡付款。
新增付款方式
由於之前的努力,當老闆要我們再新增一個付款方式,我們不必再擴充原本的OrderManager
,只要新增另一個實作PayMethodInterFace
的類別即可。
例如,要新增Apple Pay,只要新增一個繼承PayMethodInterFace
的ApplePay
物件,就可以直接丟給OrderManager
來做付款了:
$orderManager = new OrderManager();
$orderManager->pay(new ApplePay);
結論
前面所舉的例子,就是Strategy Pattern的應用。
讓我們回到比較制式化的說法,根據GoF的物件導向設計模式(Design Patterns)中關於的Strategy Pattern的說法:
定義一整族演算法,將每一個演算法封裝起來,可互換使用,更可在不影響外界的情況下,個別抽換所引用的演算法。
前面示範的CreditCard
、ApplePay
等實作PayMethodInterFace
的類別們,就是所謂的一整族演算法,它們將各種付款行為封裝起來,可以在不影響OrderManager
的情況下,根據不同情境使用不同的付款方式。
其實這也做到了SOLID
原則中的O,開放封閉原則The Open-Closed Principle,不需要修改重構後的OrderManager
,就可以透過注入不同的PayMethod物件,來改變OrderManager
的行為。
後話
之前在重構公司的結帳流程時,確實利用了這樣的方法,讓程式碼化簡,也更好維護了。雖然要將原本混亂的程式碼整理出一套邏輯很費神,而且重構也有一定的風險,但為了自己日後上班的心情(減少bug跟加班XD),還有公司業務能不能順利推廣,我認為是值得的投資。
參考