有些畫室,會設一個「傳令」的角色
主顧要委託一幅畫,先找傳令;傳令再把需求轉達給真正動筆的畫家
如果這位傳令,真的懂畫、能幫主顧把模糊的需求翻譯成畫家聽得懂的指示,讓這個角色有存在的價值
但如果傳令,每一句話都是原封不動地轉述,自己不做任何判斷、不添加任何價值
那主顧為什麼不直接去找畫家?
系列一路走來,OrderProcessor 累積了不少方法:處理訂單、算運費、判斷免運資格、拼會員徽章……
某次重構,團隊想「讓呼叫端不要直接依賴 OrderProcessor」,於是加了一層 OrderService:
public class OrderService
{
private readonly OrderProcessor _processor;
public OrderService(OrderProcessor processor)
{
_processor = processor;
}
public decimal Process(OrderRequest request) =>
_processor.Process(request);
public decimal CalculateShippingFee(decimal weight, string zone, bool isExpress) =>
_processor.CalculateShippingFee(weight, zone, isExpress);
public bool IsEligibleForFreeShipping(OrderRequest request) =>
_processor.IsEligibleForFreeShipping(request);
public string BuildLoyaltyBadge(Customer customer) =>
_processor.BuildLoyaltyBadge(customer);
}
四個公開方法,每一個都只做同一件事: 原封不動地把呼叫轉給 _processor
新人讀到 OrderService,會很自然地問:「這一層存在的意義是什麼?」
答案通常是「為了解耦」,但實際打開來看,它沒有解耦任何東西
呼叫端一樣得知道 OrderRequest、Customer 這些 OrderProcessor 需要的型別,一樣得傳入一模一樣的參數
它唯一造成的差別是,追蹤一段邏輯時,得先跳進 OrderService,才能跳到真正做事的 OrderProcessor
多繞一層,卻沒有換來任何實質的好處
之後每次 OrderProcessor 新增一個公開方法,維護的人得記得順手在 OrderService 裡也加一個一模一樣的轉發方法<不然這層「服務」,很快就會跟真正的實作對不上
這其實是懶惰的類別(Day 21)的另一種樣貌
只是這次它披著「服務層」「Facade」這種聽起來很正式的外衣,讓人比較不容易第一眼就看穿它其實什麼事都沒做
解法是移除中間人 (Remove Middle Man):讓呼叫端直接依賴 OrderProcessor,砍掉這層沒有附加價值的轉發
public class CheckoutFlow
{
private readonly OrderProcessor _processor;
public CheckoutFlow(OrderProcessor processor)
{
_processor = processor;
}
public decimal Checkout(OrderRequest request) => _processor.Process(request);
}
OrderService 整個類別可以安全刪除,「它從來沒有真正解決過耦合的問題,只是把耦合換了一個名字」
如果日後真的想要一層「應用服務」,它該做的,是加入真正屬於應用邏輯的判斷(例如:組合多個領域操作、處理交易邊界),而不是把底層方法照樣抄一遍
有幾種情況,「只會轉發」的類別,是刻意且必要的設計:
這些情況下,中間人承擔了明確的職責(存取控制、功能擴充、簡化介面、隔離依賴),不是單純的轉發
差別在於,它有沒有真正解決一個問題,還是只是換了個名字重複同一件事
23 種壞味道,四個模組,都走完了
明天是這個系列的最後一天
不談新的壞味道,談的是:離開這 30 天之後,怎麼把這份判斷力,留在下一次 commit 裡