畫室裡,構圖草稿通常只有一份正本
如果同一張草稿,被謄抄了五份,分別放在五個抽屜
日後只要構圖有一點點調整,就得找出全部五份,一份一份修改漏改一份,這幅畫的五個版本,就再也對不上彼此了
Day 19 把「免運判斷」整理進了 OrderProcessor:
public bool IsEligibleForFreeShipping(OrderRequest request)
{
bool isVip = request.Tier == CustomerTier.Vip;
bool meetsStandardThreshold = request.Line.Qty * 100 >= 1000;
bool meetsConvenienceStoreThreshold =
request.DeliveryMethod == "超商" &&
request.Line.Qty * 100 >= 700 &&
!request.IsFragile;
return isVip || meetsStandardThreshold || meetsConvenienceStoreThreshold;
}
同一段時間,另一位工程師接到需求:購物車頁面要顯示「還差多少錢免運」的提示
他不知道 OrderProcessor 已經有這條規則,於是在 CartPreviewService 裡,重新寫了一次:
public class CartPreviewService
{
public string GetFreeShippingHint(Cart cart, Customer customer)
{
bool isVip = customer.Tier == CustomerTier.Vip;
bool meetsStandardThreshold = cart.Qty * 100 >= 1000;
bool meetsConvenienceStoreThreshold =
cart.DeliveryMethod == "超商" &&
cart.Qty * 100 >= 700 &&
!cart.IsFragile;
bool isEligible = isVip || meetsStandardThreshold || meetsConvenienceStoreThreshold;
return isEligible ? "已享免運!" : "再買一點就免運囉";
}
}
同一條規則,兩個檔案,兩份幾乎一模一樣的邏輯
一個月後,業務又調整規則:VIP 免運的資格,改成需要當月消費滿一次才算數
工程師改了 OrderProcessor.IsEligibleForFreeShipping,上線前也記得測試過了,一切正常
但 CartPreviewService.GetFreeShippingHint 裡的那一份,完全沒有人知道要一起改
結果購物車顯示「已享免運」,結帳時卻被要求付運費
客訴湧入客服信箱,沒有人一開始想得到問題出在兩份「看起來一樣」的規則,悄悄分岔了
兩份草稿,只要有一份被修改而另一份沒有跟上
就會產生看不見的裂縫,直到有人真的去比對兩份草稿,才會發現它們早就不一樣了
解法的核心思想:這條規則,只該有一份「真相」,其他所有需要用到它的地方,都只能來查閱,不能自己謄抄
OrderProcessor 跟 CartPreviewService 是兩個互不相關的類別
適合用提煉類別 (Extract Class),建立一個新的、專屬的政策類別:
public class ShippingEligibilityPolicy
{
public bool IsEligibleForFreeShipping(int qty, CustomerTier tier, string deliveryMethod, bool isFragile)
{
bool isVip = tier == CustomerTier.Vip;
bool meetsStandardThreshold = qty * 100 >= 1000;
bool meetsConvenienceStoreThreshold =
deliveryMethod == "超商" && qty * 100 >= 700 && !isFragile;
return isVip || meetsStandardThreshold || meetsConvenienceStoreThreshold;
}
}
OrderProcessor 跟 CartPreviewService,都改成向這個唯一的政策類別詢問答案:
public class OrderProcessor
{
private readonly ShippingEligibilityPolicy _shippingPolicy;
public bool IsEligibleForFreeShipping(OrderRequest request) =>
_shippingPolicy.IsEligibleForFreeShipping(
request.Line.Qty, request.Tier, request.DeliveryMethod, request.IsFragile);
}
public class CartPreviewService
{
private readonly ShippingEligibilityPolicy _shippingPolicy;
public string GetFreeShippingHint(Cart cart, Customer customer)
{
bool isEligible = _shippingPolicy.IsEligibleForFreeShipping(
cart.Qty, customer.Tier, cart.DeliveryMethod, cart.IsFragile);
return isEligible ? "已享免運!" : "再買一點就免運囉";
}
}
下一次規則異動,只要改 ShippingEligibilityPolicy 一個地方
購物車的提示,跟結帳時的實際判斷,保證永遠是同一份規則,不會再分岔
今天的例子,重複發生在兩個無關的類別中,但重複其實有好幾種常見的樣貌:
if/else 兩個分支裡,都執行了同一段程式碼,把它搬到判斷式外面明天我們看一個相反的極端:一個類別,存在感薄弱到,拿掉它,好像也沒差
模組四第三站:懶惰的類別(Lazy Class)