Visitor Pattern — Посетитель

Алгоритм отдельно от структуры. Можно добавлять новые операции над объектами без изменения этих объектов.

Проблема

Есть иерархия классов (узлы AST, элементы UI, продукты в каталоге). Нужно добавить несколько разных операций (export to PDF, calculate cost, validate). Добавлять в каждый класс метод — нарушение SRP и Open-Closed.

Решение

  • Структуру (классы) не трогаем.
  • Делаем visitor — отдельный класс с методами для каждого типа узла.
  • Узлы реализуют один метод accept(visitor), который вызывает нужный метод visitor'а (по типу).

Пример в JS

// Иерархия товаров
class Book {
  accept(visitor) { return visitor.visitBook(this); }
}
class CD {
  accept(visitor) { return visitor.visitCD(this); }
}
class DVD {
  accept(visitor) { return visitor.visitDVD(this); }
}

// Visitor — расчёт цены
class PriceVisitor {
  visitBook(b) { return 10; }
  visitCD(c) { return 8; }
  visitDVD(d) { return 15; }
}

// Visitor — экспорт
class ExportVisitor {
  visitBook(b) { return `Book: ${b.title}`; }
  visitCD(c) { return `CD: ${c.album}`; }
  visitDVD(d) { return `DVD: ${d.movie}`; }
}

const items = [new Book, new CD, new DVD];
const price = items.reduce((sum, i) => sum + i.accept(new PriceVisitor), 0);
const exported = items.map((i) => i.accept(new ExportVisitor));
// В JS можно проще — без accept, через switch по типу
const visit = (visitor) => (item) => visitor[item.type](item);
const price = visit({
  book: (b) => 10,
  cd: (c) => 8,
  dvd: (d) => 15,
});
items.map(price);

Где используется в JS-экосистеме

  • Babel/SWC transformers — visitor pattern для AST
  • TypeScript compilerts.visitNode, ts.visitEachChild
  • PostCSS plugins — visitor для CSS-узлов
  • Marpit/MDX rendering — обход markdown AST
  • GraphQL: graphql.visit — обход GraphQL AST

Подводные камни

  • Double dispatch: классический Visitor требует, чтобы класс знал о visitor (через accept). В JS можно обойтись словарём.
  • Visitor нарушает Open-Closed для структуры: если добавили новый тип узла — нужно дописать метод во все visitor'ы.
  • Visitor хорош, когда структура стабильна, а операции растут. Если наоборот — лучше методы в классах.

Главные тезисы автора

  • «Visitor делает слабо зацепленным алгоритм и таргет».
  • «Позволяет добавлять новые операции так, что мы не знаем, над какими инстансами».
  • «Слабое зацепление объекта и субъекта».
  • В JS-экосистеме главный пример — обход AST в трансформаторах кода.
  • Часто в паре с Interpreter (visitor обходит AST интерпретатора).

🎓 Источники

  • 🎓 GoF Patterns Обзор всех паттернов · 2025-04-29
    • Visitor разделяет алгоритм и таргет
    • Новые операции без знания инстансов
    • Слабое зацепление субъекта и объекта

См. также