畫室主人想知道,紅色顏料還剩多少
合理的做法,是直接問管理顏料的那位學徒不合理的做法,是主人得先問大學徒「顏料放在哪個房間」,再問那個房間的學徒「放在哪個櫃子」
再問管櫃子的人「哪一格抽屜」,最後才問到真正管顏料的人問一件小事,繞了五個人
不是因為顏料很難找,是因為沒有人幫主人把這條路,事先鋪好
系統裡有這樣的巢狀結構:
public class Address
{
public string Street { get; set; }
public string Zone { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public Address ShippingAddress { get; set; }
}
public class Customer
{
public string Name { get; set; }
public Order LastOrder { get; set; }
}
有個功能,要判斷客戶最後一筆訂單的收貨地區,是不是能享有當日速達:
public class ExpressEligibilityChecker
{
public bool IsEligibleForExpress(Customer customer)
{
string zone = customer.LastOrder.ShippingAddress.Zone;
return zone == "北" || zone == "中";
}
}
customer.LastOrder.ShippingAddress.Zone一行程式碼,串起了三個物件
ExpressEligibilityChecker 為了問到 Zone,被迫連帶知道了:
Customer 有一個 LastOrder
Order 有一個 ShippingAddress
Address 有一個 Zone
它本來只該認識 Customer 這位「密友」,卻被迫也認識了 Order、Address 的內部長相
這條鏈,只要中間任何一環改變LastOrder 改名成 RecentOrder,或者 Address 換了一種結構
所有沿用這條鏈的程式碼,都會一起壞掉
這正是物件導向裡「迪米特法則」(最少知識原則)要提醒我們的事
只跟你的密友說話,不要繞過密友,直接向密友的密友問問題
要幫這樣的程式碼寫測試,也會變得很痛苦
得先組出一個完整的 Customer → Order → Address 巢狀假物件,才能測一個簡單的判斷
解法是隱藏委派 (Hide Delegate):
在鏈條的每一站,都建立一個方法,把「怎麼找到下一站」的細節,藏在物件內部
先讓 Order 自己知道怎麼回答「收貨地區」:
public class Order
{
public int OrderId { get; set; }
public Address ShippingAddress { get; set; }
public string GetShippingZone() => ShippingAddress.Zone;
}
再讓 Customer 把請求,轉交給 LastOrder:
public class Customer
{
public string Name { get; set; }
public Order LastOrder { get; set; }
public string GetLastOrderShippingZone() => LastOrder.GetShippingZone();
}
呼叫端,現在只需要跟它的密友 Customer 對話:
public class ExpressEligibilityChecker
{
public bool IsEligibleForExpress(Customer customer)
{
string zone = customer.GetLastOrderShippingZone();
return zone == "北" || zone == "中";
}
}
ExpressEligibilityChecker 現在只認識 Customer 一個類別
不再需要知道 Order 或 Address 的存在
未來 Customer 內部要換一種方式儲存訂單歷史
只要 GetLastOrderShippingZone() 這個對外的契約沒變,呼叫端完全不用跟著修改
如果是 「短而穩定的鏈」
例如只串兩層,而且結構幾乎不會變動,通常是可以接受的,不需要為了原則而過度重構
真正該處理的,是那種跨越三層以上、或者串接的結構本身就容易變動的鏈
判斷的關鍵不是「有沒有連續呼叫」
是這條鏈,有沒有讓呼叫端知道了太多它不該知道的內部結構
還有一個更重要的提醒,如果把鏈條上每一站的委派都無腦隱藏,鏈條起點的物件
可能會被塞滿一堆 「只是轉發」 的方法,變成明天要談的下一種壞味道
明天是模組五的最後一站,也是這 23 種壞味道裡的最後一種
一個角色,只負責把話轉過去,自己不做任何判斷
中間人(Middle Man)