iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Software Development

文藝復興:這段程式碼,好像有點味道系列 第 29

Day 29|只負責轉達話語,自己不畫一筆的傳令:中間人 (Middle Man)

  • 分享至 

  • xImage
  •  

有些畫室,會設一個「傳令」的角色

主顧要委託一幅畫,先找傳令;傳令再把需求轉達給真正動筆的畫家

如果這位傳令,真的懂畫、能幫主顧把模糊的需求翻譯成畫家聽得懂的指示,讓這個角色有存在的價值

但如果傳令,每一句話都是原封不動地轉述,自己不做任何判斷、不添加任何價值
那主顧為什麼不直接去找畫家?

一層只會轉發的服務

系列一路走來,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,會很自然地問:「這一層存在的意義是什麼?」

答案通常是「為了解耦」,但實際打開來看,它沒有解耦任何東西

呼叫端一樣得知道 OrderRequestCustomer 這些 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 整個類別可以安全刪除,「它從來沒有真正解決過耦合的問題,只是把耦合換了一個名字」

如果日後真的想要一層「應用服務」,它該做的,是加入真正屬於應用邏輯的判斷(例如:組合多個領域操作、處理交易邊界),而不是把底層方法照樣抄一遍

中間人,不是永遠該被移除

有幾種情況,「只會轉發」的類別,是刻意且必要的設計:

  • 代理模式 (Proxy):用一層代理,控制對真正物件的存取(例如延遲載入、權限檢查)
  • 裝飾器模式 (Decorator):在不改動原物件的前提下,動態疊加新行為
  • 外觀模式 (Facade):把一個複雜子系統,包裝成一個簡單、穩定的對外介面
  • 刻意隔離兩個模組:讓兩邊不直接依賴彼此,換取未來抽換其中一邊的彈性

這些情況下,中間人承擔了明確的職責(存取控制、功能擴充、簡化介面、隔離依賴),不是單純的轉發

差別在於,它有沒有真正解決一個問題,還是只是換了個名字重複同一件事

怎麼判斷,這層轉發是不是多餘的

  • 這個類別的方法,是不是幾乎都只有一行,內容都是呼叫另一個物件的同名方法?
  • 如果把這個類別拿掉,呼叫端直接找真正做事的物件,會不會更清楚、更直接
  • 這層存在,是為了某個具體的職責(控制存取、擴充行為、簡化介面),還是只是「感覺應該要有一層」?

自我檢查清單

  1. 這個類別的公開方法,是不是大多數都只有一行,內容是轉發呼叫?
  2. 如果有人問「這個類別做了什麼」,我的答案是不是「它只是呼叫了另一個類別」?
  3. 這一層的存在,是不是某次隱藏委派(Day 28)重構做得太徹底的副作用?
  4. 這層轉發,有沒有承擔真正的職責(控制存取、擴充行為、簡化介面、隔離依賴)?
  5. 如果拿掉這一層,讓呼叫端直接依賴真正做事的物件,程式碼會不會更透明?

明日預告

23 種壞味道,四個模組,都走完了

明天是這個系列的最後一天
不談新的壞味道,談的是:離開這 30 天之後,怎麼把這份判斷力,留在下一次 commit 裡


上一篇
Day 28|問個顏料存量,要問過五個學徒才有答案:訊息鏈 (Message Chains)
系列文
文藝復興:這段程式碼,好像有點味道29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言