Коротка відповідь
middleVisitor — це патерн, що дозволяє додавати нові операції до існуючих об’єктів без зміни їх класів. Він розділяє структуру (елементи) від поведінки (відвідувачів). Реалізується через інтерфейс Visitor з методами visitX для кожного елементу, а елементи мають accept(visitor). Приклад: `class ConcreteElementA { accept(v){v.visitA(this)} }`
Повне пояснення
1. Що це і навіщо
Visitor – патерн поведінки, що дозволяє виконувати різні операції над елементами структури без зміни самих класів. Застосовується, коли потрібна розширюваність операцій над складною ієрархією об’єктів, наприклад, AST‑парсерів, графів чи UI‑компонентів.
2. Принцип роботи / алгоритм
Патерн передбачає дві ролі: Element (або Component) і Visitor. Кожен елемент реалізує метод accept(visitor); він викликає відповідний метод у відвідувачі, передаючи сам себе. Відвідувач має окремі методи visitX(element) для кожного типу елемента. Це дозволяє додавати нові операції, створюючи новий клас Visitor.
3. Реалізація (JS / TS)
- Базова реалізація
interface Visitor { visitA(a: ConcreteA): void; visitB(b: ConcreteB): void }
interface Element { accept(v: Visitor): void }
class ConcreteA implements Element{ accept(v){v.visitA(this)} }
class ConcreteB implements Element{ accept(v){v.visitB(this)} }
- Реалістичний приклад – дерево UI‑компонентів, де Visitor підраховує кількість елементів і рендерить їх у DOM.
class RenderVisitor implements Visitor{ visitButton(b){ document.body.appendChild(b.render()) } ... }
- Спрощений для новачка – список товарів, де Visitor підраховує суму цін.
class SumVisitor implements Visitor{ total=0; visitProduct(p){ this.total+=p.price } }
4. Тестування (Jest або Vitest)
test('SumVisitor correctly sums prices', () => {
const items: Element[] = [new Product(10), new Product(20)];
const visitor = new SumVisitor();
items.forEach(i => i.accept(visitor));
expect(visitor.total).toBe(30);
});
5. Проблеми та edge cases
- Неправильне використання ролей – елемент, що не реалізує
accept, викликає помилку. - Зайва абстракція – надмірна кількість Visitor‑ів для простих операцій.
- Cyclical dependencies – елемент, що викликає Visitor, який в свою чергу звертається назад до елемента.
- Плутанина між Visitor і Strategy – обидва розділяють поведінку, але Visitor працює над структурою, а Strategy – над алгоритмом.
- Performance‑пастки – при великій кількості елементів і Visitor‑ів накладається великий overhead на виклики.
6. Оптимізація та продуктивність
Visitor додає один рівень абстракції, що збільшує час виконання на ~5‑10 %. Пам’ять використовується лише для об’єктів Visitor, що зазвичай невелика. Для масштабованих систем варто кешувати результати Visitor‑ів.
7. Без JS / архітектурні альтернативи
У чистій HTML/CSS можна досягти подібної роздільності через slot і custom elements, де логіка рендеру відокремлюється у Web Components. У серверних мовах без патерна можна просто викликати функції над об’єктами, але це не дає гнучкості додавати нові операції без зміни класів.
8. Cheatsheet + follow-up
- Cheatsheet: Element → accept(visitor); Visitor → visitX(element).
- Interview Q1: Як Visitor допомагає уникнути «шляху» (open‑closed principle)?
- Interview Q2: Які ризики виникають при додаванні нового елемента до ієрархії?