cv.cm

Pemrograman · Pola desain

Penjelasan pola Visitor beserta contohnya

5 menit baca

Jawaban singkat

Visitor adalah pola desain behavioral yang memungkinkan Anda menambah operasi baru ke sekelompok class terkait tanpa mengubah class tersebut. Setiap operasi ditulis sebagai objek visitor dengan satu method per tipe elemen, dan setiap elemen punya method accept(visitor) yang memanggil method visitor yang cocok. Teknik ini, yang disebut double dispatch, memilih kode yang dijalankan berdasarkan tipe elemen sekaligus tipe visitor. Compiler, linter, dan code transformer memakai visitor untuk menelusuri syntax tree.

Tujuan dan masalah yang diselesaikan

Tujuan: merepresentasikan operasi yang akan dilakukan pada elemen-elemen struktur objek. Visitor memungkinkan Anda mendefinisikan operasi baru tanpa mengubah class elemen yang dioperasikannya. Ini salah satu pola behavioral dalam buku Gang of Four (GoF).

Misalkan program menggambar punya tiga class bentuk: Circle, Rectangle, dan Group. Seiring waktu Anda butuh banyak operasi pada mereka: menghitung total luas, ekspor ke SVG, ekspor ke JSON, menghitung jumlah bentuk, memeriksa tumpang tindih. Jika setiap operasi menjadi method di setiap class bentuk, class bentuk membengkak dengan kode yang tidak ada hubungannya dengan menjadi sebuah bentuk, dan setiap operasi baru menyentuh semuanya.

Dengan Visitor, setiap operasi menjadi satu class (visitor luas, visitor SVG) yang berisi kode untuk setiap tipe bentuk. Bentuk hanya butuh satu method generik, accept. Menambah operasi berarti menulis satu visitor baru; class bentuk tidak berubah.

Struktur dan peserta

PeranTanggung jawab
VisitorInterface dengan satu method visit per tipe elemen konkret, seperti visitCircle dan visitRectangle
Concrete visitorMengimplementasikan satu operasi untuk semua tipe elemen dan dapat mengumpulkan hasil
ElementInterface dengan method accept(visitor)
Concrete elementMengimplementasikan accept dengan memanggil method visit untuk tipenya sendiri, sambil mengoper dirinya
Object structureKoleksi atau pohon elemen, seperti dokumen atau syntax tree, yang ditelusuri visitor

Mengapa accept dibutuhkan: di TypeScript, Java, dan C#, pemanggilan method memilih implementasinya dari tipe objek tempat method dipanggil (single dispatch). Pemanggilan accept melakukan dispatch pada tipe elemen, dan pemanggilan di dalamnya, seperti visitor.visitCircle(this), melakukan dispatch pada tipe visitor. Keduanya bersama-sama membentuk double dispatch.

Implementasi TypeScript

Interface visitor bersifat generik pada tipe kembaliannya, sehingga satu visitor dapat menghitung angka dan visitor lain dapat menghasilkan string.

interface ShapeVisitor<R> {
  visitCircle(c: Circle): R;
  visitRectangle(r: Rectangle): R;
  visitGroup(g: Group): R;
}
interface Shape {
  accept<R>(visitor: ShapeVisitor<R>): R;
}

class Circle implements Shape {
  constructor(readonly radius: number) {}
  accept<R>(v: ShapeVisitor<R>) { return v.visitCircle(this); }
}
class Rectangle implements Shape {
  constructor(readonly width: number, readonly height: number) {}
  accept<R>(v: ShapeVisitor<R>) { return v.visitRectangle(this); }
}
class Group implements Shape {
  constructor(readonly children: Shape[]) {}
  accept<R>(v: ShapeVisitor<R>) { return v.visitGroup(this); }
}

class AreaVisitor implements ShapeVisitor<number> {
  visitCircle(c: Circle) { return Math.PI * c.radius ** 2; }
  visitRectangle(r: Rectangle) { return r.width * r.height; }
  visitGroup(g: Group): number {
    return g.children.reduce((sum, child) => sum + child.accept(this), 0);
  }
}

class SvgVisitor implements ShapeVisitor<string> {
  visitCircle(c: Circle) { return '<circle r="' + c.radius + '"/>'; }
  visitRectangle(r: Rectangle) { return '<rect width="' + r.width + '" height="' + r.height + '"/>'; }
  visitGroup(g: Group): string { return "<g>" + g.children.map((c) => c.accept(this)).join("") + "</g>"; }
}

const drawing = new Group([new Circle(1), new Rectangle(2, 3), new Group([new Rectangle(1, 1)])]);
console.log(drawing.accept(new AreaVisitor()).toFixed(2)); // 10.14
console.log(drawing.accept(new SvgVisitor()));
// <g><circle r="1"/><rect width="2" height="3"/><g><rect width="1" height="1"/></g></g>

Operasi ketiga, seperti menghitung jumlah bentuk, akan menjadi class baru yang mengimplementasikan ShapeVisitor<number>. Tidak ada class bentuk yang berubah.

Alternatif TypeScript: union type dan switch

Di TypeScript, discriminated union dengan switch yang exhaustive memberi banyak manfaat yang sama dengan kode lebih sedikit. Compiler melaporkan error jika ada kasus yang hilang, yang menggantikan keamanan yang diberikan interface visitor:

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number }
  | { kind: "group"; children: Shape[] };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "rect": return s.width * s.height;
    case "group": return s.children.reduce((sum, c) => sum + area(c), 0);
    default: {
      const unreachable: never = s; // compile error if a kind is not handled
      return unreachable;
    }
  }
}

console.log(area({ kind: "group", children: [{ kind: "rect", width: 2, height: 3 }] })); // 6

Setiap operasi baru adalah fungsi baru. Gaya ini cocok untuk data biasa, seperti JSON atau syntax tree, dan dipakai banyak basis kode TypeScript sebagai pengganti visitor berbasis class.

Kapan dipakai dan kapan tidak

  • Pakai bila Anda punya kumpulan class elemen yang stabil dan butuh banyak operasi yang tidak berkaitan pada mereka, seperti evaluasi, pencetakan, pengecekan tipe, dan optimasi pada syntax tree.
  • Pakai untuk menjaga kode operasi tetap bersama: seluruh ekspor SVG berada di satu visitor, tidak tersebar di banyak class.
  • Pakai bila visitor perlu mengakumulasi state saat menelusuri struktur, seperti symbol table atau total berjalan.
  • Jangan dipakai bila tipe elemen sering berubah; setiap perubahan merambat ke semua visitor.
  • Jangan dipakai bila elemennya sedikit dan operasinya sederhana. Method pada class, atau fungsi dengan switch, lebih mudah dibaca.
  • Perlu diingat bahwa visitor biasanya butuh akses ke data elemen, yang bisa mendorong Anda menjadikan field publik.

Penggunaan di dunia nyata

  • Plugin Babel adalah visitor: objek dengan method yang dinamai menurut tipe node, seperti Identifier atau CallExpression, yang dipanggil @babel/traverse saat menelusuri syntax tree.
  • Aturan ESLint mengembalikan objek berisi method visitor dengan kunci tipe node, dan ESLint memanggilnya selama traversal.
  • Class ast.NodeVisitor dan ast.NodeTransformer di Python memanggil method visit_ yang dinamai menurut class node, seperti visit_Name.
  • java.nio.file.FileVisitor di Java, dipakai dengan Files.walkFileTree, menerima callback untuk setiap direktori dan file dalam pohon file.

Pola terkait

PolaHubungannya dengan Visitor
CompositeVisitor sering diterapkan pada struktur Composite; method visit sebuah grup mengunjungi anak-anaknya, seperti pada contoh.
InterpreterInterpreter mengevaluasi syntax tree; visitor dapat mengimplementasikan evaluasi itu, ditambah operasi lain pada pohon yang sama.
IteratorIterator menelusuri elemen; visitor mendefinisikan operasi pada setiap elemen. Keduanya dapat digabung.
StrategyStrategy memvariasikan satu algoritma untuk satu context; visitor menggabungkan satu operasi untuk banyak tipe elemen.

FAQ

Apa itu double dispatch?

Double dispatch berarti kode yang dijalankan bergantung pada tipe dua objek. Pada Visitor, element.accept(visitor) memilih tipe elemen, dan visitor.visitX(this) di dalamnya memilih tipe visitor.

Kapan sebaiknya tidak memakai pola Visitor?

Hindari bila tipe elemen baru sering ditambahkan, karena setiap tipe baru membutuhkan perubahan di setiap visitor. Pola ini juga menambah seremoni yang tidak sepadan untuk beberapa operasi sederhana.

Apakah Visitor berguna di TypeScript?

Berguna untuk struktur objek berbasis class. Untuk data biasa, discriminated union dengan switch yang exhaustive biasanya memberi hasil yang sama dengan kode lebih sedikit dan pengecekan tipe penuh.

Mengapa Babel dan ESLint memakai visitor?

Keduanya bekerja pada syntax tree dengan banyak tipe node dan mendukung banyak plugin atau aturan yang independen. Visitor memungkinkan setiap plugin atau aturan menangani hanya tipe node yang diminatinya, sementara tool menangani traversalnya.

Semua tutorial pemrograman