interface 與組合
interface 與組合
Go 沒有 implements 關鍵字,介面是隱式滿足的。這讓「組合勝繼承」變得自然,也讓 gortex 能把 tracing 從 in-memory 換成 OTel 而不動業務碼。
小介面與隱式滿足
一個型別只要有介面要的方法,就自動滿足那個介面,不用宣告:
type Stringer interface { String() string }
type Point struct{ X, Y int }
func (p Point) String() string { return fmt.Sprintf("(%d,%d)", p.X, p.Y) }
// Point 自動是 Stringer,沒寫任何 implements
慣例是介面要小。標準庫的 io.Reader、io.Writer 都只有一個方法,所以到處能組。兩條 Go 諺語講得很準:「介面越大,抽象越弱」、「接受介面,回傳具體型別」。函式參數收小介面,呼叫端能塞的東西就最多。
組合勝繼承:embedding
Go 沒有繼承,用 embedding(嵌入)做組合 + 方法提升:
type Server struct {
*log.Logger // 嵌入;Server 直接有 Logger 的 Printf 等方法
}
s := &Server{Logger: log.Default()}
s.Printf("up") // 來自嵌入的 Logger
type ReadWriter interface { // 介面也能嵌
io.Reader
io.Writer
}
這不是 subclass,只是把嵌入型別的方法集提升到外層。要覆蓋就在外層定義同名方法蓋掉,沒有 super 那套。
依賴反轉與 adapter
對小介面寫程式、而不是對具體型別,實作就能換。adapter 則是把外來型別包一層,拼出你要的介面。
gortex 的 tracing 就是現成範例:框架只依賴一個 Tracer 介面,預設掛 in-memory 的 SimpleTracer;要上 OpenTelemetry 就換一個 OTel adapter 實作同一組介面,業務碼一行都不用改(見 B7〈可觀測性:健康檢查與 tracing〉,gortex-observability-health-tracing)。
Effective Go 精選:Interfaces、Embedding
Effective Go 的 Interfaces and methods 講介面就是一組方法、一個型別可同時滿足多個介面。Embedding 講介面與 struct 的嵌入、方法如何提升、以及為什麼這不是繼承。兩節合起來,就是 Go 組織程式碼的主軸:用組合與小介面,而不是型別階層。
重點整理
- 介面隱式滿足:型別有方法就算數,不用宣告 implements。
- 小介面最有用:「介面越大,抽象越弱」。
- embedding 是組合 + 方法提升,不是繼承;同名方法可覆蓋。
- 對介面寫程式 + adapter,讓實作可抽換;gortex 的
Tracer就是這套(見 B7)。 - 介面驅動設計貫穿整個框架,後面框架篇會一直看到。
延伸:本系列接著用這些觀念拆 gortex 框架,原始碼見 yshengliao/gortex。
大綱 Sheng,內文 Claude 協助 · 環境 Go 1.24(當時最新 release)· 本系列為事後回填整理 · 列入 20260613 blog 翻新計劃,新漆未乾。