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 compiler —
ts.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 разделяет алгоритм и таргет
- Новые операции без знания инстансов
- Слабое зацепление субъекта и объекта