Visitor — это поведенческий паттерн, который позволяет добавлять новые операции к объектам, не изменяя их классы.
Простыми словами: налоговый инспектор (visitor) приходит в разные компании и выполняет проверку — компании не меняются, а новые виды проверок добавляются легко.
▪️ Пример:
Система документооборота: есть разные типы документов, и нужно добавлять операции (экспорт, валидация, подсчёт статистики) без изменения классов документов.
// Visitor
interface DocumentVisitor {
void visit(Invoice invoice);
void visit(Contract contract);
void visit(Report report);
}
// Элементы
interface Document {
void accept(DocumentVisitor visitor);
}
class Invoice implements Document {
private final BigDecimal amount;
public Invoice(BigDecimal amount) { this.amount = amount; }
public BigDecimal getAmount() { return amount; }
public void accept(DocumentVisitor visitor) {
visitor.visit(this); // double dispatch
}
}
class Contract implements Document {
private final LocalDate expiryDate;
public Contract(LocalDate expiryDate) { this.expiryDate = expiryDate; }
public LocalDate getExpiryDate() { return expiryDate; }
public void accept(DocumentVisitor visitor) {
visitor.visit(this);
}
}
class Report implements Document {
private final int pageCount;
public Report(int pageCount) { this.pageCount = pageCount; }
public int getPageCount() { return pageCount; }
public void accept(DocumentVisitor visitor) {
visitor.visit(this);
}
}
// Конкретный visitor — новая операция без изменения документов
class ExportVisitor implements DocumentVisitor {
public void visit(Invoice invoice) {
System.out.println("Экспорт счёта: " + invoice.getAmount() + " ₽");
}
public void visit(Contract contract) {
System.out.println("Экспорт договора до " + contract.getExpiryDate());
}
public void visit(Report report) {
System.out.println("Экспорт отчёта: " + report.getPageCount() + " стр.");
}
}
// Использование
List<Document> docs = List.of(
new Invoice(new BigDecimal("150000")),
new Contract(LocalDate.of(2027, 1, 1)),
new Report(42)
);
DocumentVisitor exporter = new ExportVisitor();
docs.forEach(doc -> doc.accept(exporter));
▪️ Когда использовать
— Нужно выполнить операцию над группой разнородных объектов
— Новые операции добавляются часто, а новые типы элементов — редко
— Пример из JDK: java.nio.file.FileVisitor
▪️ Минус
Visitor нужно обновлять при добавлении нового типа элемента — нарушается Open/Closed Principle для элементов.
🐸 Библиотека собеса по Java
#patterns