Ce este
Șablonul Visitor adaugă o operație nouă unei familii de tipuri fără să le modifice. Tipurile învață un singur lucru, o singură dată — Accept(visitor) — și, de atunci înainte, fiecare operație nouă este doar un vizitator nou, cu câte o metodă pentru fiecare tip.
El inversează compromisul obișnuit: adăugarea de operații devine ușoară, iar adăugarea de tipuri devine schimbarea costisitoare.
Exemplu: operații peste forme
Formele nu se mai schimbă niciodată. Afișarea ariilor este un vizitator, denumirea lor este altul — iar un vizitator de export în SVG, scris mâine, nu ar atinge nici Circle, nici Rectangle.
Operații noi, tipuri neatinse
Ambii vizitatori parcurg aceeași listă de forme prin aceeași ușă Accept. Fiecare vizitator ține întreaga operație într-un singur loc, în loc să o împrăștie prin tipuri.
Ieșire: program Go și rezultatul capturat
package main
import "fmt"
type Shape interface {
Accept(v Visitor)
}
type Circle struct {
Radius float64
}
func (c Circle) Accept(v Visitor) { v.VisitCircle(c) }
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Accept(v Visitor) { v.VisitRectangle(r) }
// Un vizitator este o operație întreagă peste fiecare tip de formă.
type Visitor interface {
VisitCircle(c Circle)
VisitRectangle(r Rectangle)
}
type AreaPrinter struct{}
func (AreaPrinter) VisitCircle(c Circle) {
fmt.Printf("aria cercului: %.2f\n", 3.14159*c.Radius*c.Radius)
}
func (AreaPrinter) VisitRectangle(r Rectangle) {
fmt.Printf("aria dreptunghiului: %.2f\n", r.Width*r.Height)
}
type NamePrinter struct{}
func (NamePrinter) VisitCircle(c Circle) { fmt.Println("un cerc") }
func (NamePrinter) VisitRectangle(r Rectangle) { fmt.Println("un dreptunghi") }
func main() {
shapes := []Shape{Circle{Radius: 2}, Rectangle{Width: 3, Height: 4}}
for _, s := range shapes {
s.Accept(AreaPrinter{})
}
for _, s := range shapes {
s.Accept(NamePrinter{})
}
}
Când să îl folosești
Folosește-l atunci când o familie stabilă de tipuri primește mereu operații noi — compilatoare care vizitează arbori de sintaxă, documente care capătă exportatoare, forme care capătă randatoare și instrumente de măsurare.
Renunță la el atunci când familia de tipuri încă crește: fiecare tip nou obligă la o modificare în fiecare vizitator. În multe programe Go, un type switch face aceeași treabă cu mai puțină mecanică — apelează la vizitator când se înmulțesc operațiile, nu tipurile.
Avantaje și dezavantaje
Avantaje: operațiile întregi trăiesc fiecare într-un singur tip; operațiile noi nu cer nicio modificare în tipurile vizitate; logica înrudită nu mai stă împrăștiată.
Dezavantaje: dubla dispecerizare (Accept care apelează înapoi) îi încurcă pe începători; adăugarea unui tip vizitat înseamnă editarea fiecărui vizitator; interfața familiei vizitate este înghețată devreme.