把「作用在一個物件結構上的操作」抽出來變成獨立的 visitor。要新增操作時只要新增一個 visitor,不必動到元素類別。代價是:元素型別固定,新增一種元素就得改所有 visitor。
🧠 When to Use
TIP
訊號:元素的型別集合很穩定,但你不斷要對它們加新操作(計價、匯出、檢查),而你不想每加一個操作就去戳每個元素類別。
- 對一個物件結構要做許多不相關的操作,想把操作集中、避免污染元素類別。
- 元素類別很少新增,但操作經常新增。
- 需要對 AST/檔案樹之類的異質結構做多種遍歷處理(型別檢查、產碼、統計)。
⚖️ Structure & Variants
經典版 — double dispatch(accept + visit)
interface Shape { fun accept(v: Visitor) }
interface Visitor { fun visit(c: Circle); fun visit(s: Square) }
class Circle(val r: Int) : Shape { override fun accept(v: Visitor) = v.visit(this) }
class Square(val a: Int) : Shape { override fun accept(v: Visitor) = v.visit(this) }
class AreaVisitor : Visitor { // ← 新操作 = 新 visitor
var total = 0.0
override fun visit(c: Circle) { total += Math.PI * c.r * c.r }
override fun visit(s: Square) { total += (s.a * s.a).toDouble() }
}
val shapes = listOf(Circle(2), Square(3))
val area = AreaVisitor().also { v -> shapes.forEach { it.accept(v) } }accept先依元素型別分派、再由多載visit依visitor分派,這就是 double dispatch。
搭 Composite/Iterator 遍歷結構
和「直接在元素上加方法」的取捨
- 操作分散在元素類別:加元素容易、加操作要改每個類別。
- 改用 Visitor:加操作容易(新 visitor)、加元素痛(改所有 visitor)。選哪邊取決於哪一軸比較常變。
⚠️ Misuse & Anti-patterns
- 元素型別還在劇烈變動:每加一種元素都要回頭改全部 visitor,這時 Visitor 是負債而非資產。
- visitor 需要元素的私有狀態:為了讓 visit 拿到資料,被迫把元素封裝開大洞,得失要衡量。
- 只有一兩個操作:為了一個操作架 accept/visit 的雙分派骨架,過度設計;直接寫個方法或
when更清楚。
🔑 Takeaways
- 本質是「把操作從元素抽出成 visitor」,靠 double dispatch 對「元素 × 操作」雙向分派。
- 取捨很清楚:操作易加、元素難加——只有在型別集穩定時才划算。
- 常與 Composite 樹和 Iterator 遍歷搭配使用。
No notes yet — jot your takeaways or Q&A here.