🎯 什麼情境該想到我

當你「一個類別已經沒剩多少責任、不再值得獨立存在」的時候(常見於重構後被逐步掏空的類別)——把它併回最常使用它的那個類別。也常用在「想把兩個類別的職責打散重分」時,先合再拆。

⚙️ 怎麼用(步驟 / 公式)

意圖:一個類別責任所剩無幾時,把它併回最常用它的另一個類別。

動作要點(小步安全進行):

  1. 選定「吸收方」類別(通常是最常使用來源類別的那個)。
  2. 在吸收方類別上,為來源類別的每個公開方法建立對應的(委託)方法,先轉呼叫過去。
  3. 把所有對來源類別的呼叫端,改成呼叫吸收方類別上的這些方法,測試。
  4. 用「搬移欄位」「搬移函式」把來源類別的欄位與方法逐一搬進吸收方類別。
  5. 全部搬完後,刪除已無內容的來源類別。

★ before / after(Go)

// before:TrackingInformation 已經沒什麼事做了,只是多一層
type TrackingInformation struct {
    ShippingCompany string
    TrackingNumber  string
}
func (t TrackingInformation) Display() string { return t.ShippingCompany + ": " + t.TrackingNumber }
 
type Shipment struct{ tracking TrackingInformation }
func (s Shipment) TrackingInfo() string { return s.tracking.Display() }   // ★ 純轉發
 
// after:欄位收回 Shipment,中間層消失
type Shipment struct {
    ShippingCompany string
    TrackingNumber  string
}
func (s Shipment) TrackingInfo() string { return s.ShippingCompany + ": " + s.TrackingNumber }

什麼時候該內聯(三個訊號)

訊號說明
只剩轉發型別的每個方法都只是 return s.inner.X() → 這就是 中間人
只有一個使用者,且沒有自己的不變式一個 struct 只被一個型別持有、欄位可以是任何值 → 沒有存在價值
★ 要重新切線兩個型別的職責都劃錯了 → 先全部合回去,再重新 提煉類別。 這是最重要的用法

★ Go 的版本:真正該內聯的通常是「單方法介面 + 單一實作」

// ❌ 很常見的過度抽象
type UserRepository interface {                   // ★ 只有一個實作,測試也沒在替換
    FindByID(ctx context.Context, id string) (*User, error)
}
type postgresUserRepository struct{ db *sql.DB }
 
// ✅ 內聯掉介面,直接用具體型別
type UserStore struct{ db *sql.DB }
func (s *UserStore) FindByID(ctx context.Context, id string) (*User, error) { … }

★ Go 的介面該定義在消費方(accept interfaces, return structs)。
為每個 struct 配一個同名介面(Service + IService)在 Go 沒有任何好處
——不會更解耦,只多一層間接和一個要同步維護的檔案。見 工具-針對介面編程。

判準:現在有幾個實作?只有一個,而且測試不需要替換 → 內聯掉介面。
真的要在測試裡替換 → 介面寫在使用它的那個 package,只放用到的方法。

⚠️ Go 特有:內聯型別會改變 JSON / DB 形狀

// before
{"tracking": {"company": "FedEx", "number": "123"}}
// after ★ 變成扁平的,前端/下游全部要改
{"company": "FedEx", "number": "123"}

對外 API 的形狀是契約。 內部要內聯,但對外不能變 → 保留一個獨立的 DTO 型別做序列化,
內部模型維持內聯後的樣子。別讓「內部結構」和「線上格式」共用同一個 struct。

機械化步驟

  1. 在吸收方(Shipment)建立被吸收方的所有欄位與方法,先轉發給舊型別。
  2. 把所有對舊型別的引用改成呼叫吸收方(編譯器會列出每一處)。
  3. 舊型別零引用後刪掉(go build ./... 確認)。
  4. 每步都跑測試。

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 若來源類別仍有清楚且值得獨立的職責,就別內聯,反而該保留或做提煉類別 Extract Class。

  • 反向手法:提煉類別 Extract Class。

  • ★ 不要因為「型別很小」就內聯。判準是「它有沒有自己的不變式與行為」。Money、Email、DateRange 很小但價值很高(見 以物件取代基本型別)。

  • ★ 內聯之後欄位會變多。如果吸收方本來就接近 過大類別,內聯只是把問題搬過去——先確認吸收方撐得住。

  • ★ 對外 JSON/DB schema 會變形,而且編譯器不會提醒。用獨立的 DTO 隔開內部模型與線上格式。

  • 被多個型別使用的型別不要內聯——會變成 重複程式碼。

  • 「先全部合回去再重切」要在一個 branch 內做完。合回去的中間狀態是比原本更糟的大型別,不要停在那裡。

🔗 相關工具