Короче говоря, я думаю, что шаблон Visitor ортогонален способу перечисления. Это можно сделать любым способом или вообще без перечисления.
Я думаю, что посетитель должен знать, из каких элементов состоит структура посещаемого объекта. Хотелось бы знать, что машина состоит из колес и двигателя. Думаю, знать, как именно они сочетаются, не нужно. Рассмотрим следующий пример. Инсайдер знает структуру посещаемого объекта и сам выполняет перечисление. Аутсайдер не знает об этом и передает перечисление посещаемому объекту.
interface Visitable {
void accept(Visitor visitor);
}
class WorkingRoom implements Visitable {
public int number;
WorkingRoom(int number) {
this.number = number;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class BossRoom implements Visitable {
public String bossName;
BossRoom(String bossName) {
this.bossName = bossName;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
interface Visitor{
void visit(WorkingRoom workingRoom);
void visit(BossRoom bossRoom);
void visit(Office office);
}
class Office implements Visitable{
public Visitable[] firstFloor;
public Visitable[] secondFloor;
public Visitable ceoRoom;
public Office(){
firstFloor = new Visitable[]{ new WorkingRoom(101),
new WorkingRoom(102),
new BossRoom("Jeff Atwood"),
new WorkingRoom(103)};
secondFloor = new Visitable[]{ new WorkingRoom(201),
new WorkingRoom(202),
new BossRoom("Joel Spolsky")};
ceoRoom = new BossRoom("Bill Gates");
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void showMeTheOffice(Visitor visitor, boolean sayPlease) {
// Office manager decides the order in which rooms are visited
for(int i=secondFloor.length-1; i >= 0; i--){
secondFloor[i].accept(visitor);
}
if (sayPlease){
ceoRoom.accept(visitor);
}
for (int i = 0; i < firstFloor.length; i++) {
firstFloor[i].accept(visitor);
}
}
}
class Insider implements Visitor{
public void visit(WorkingRoom workingRoom) {
System.out.println("I> This is working room #"+workingRoom.number);
}
public void visit(BossRoom bossRoom) {
System.out.println("I> Hi, "+bossRoom.bossName);
}
public void visit(Office office) {
// I know about office structure, so I'll just go to the 1st floor
for(int i=0;i<office.firstFloor.length;i++){
office.firstFloor[i].accept(this);
}
}
}
class Outsider implements Visitor{
public void visit(Office office) {
// I do not know about office structure, but I know they have a
// nice office manager
// I'll just ask to show me the office
office.showMeTheOffice(this, true);
}
public void visit(WorkingRoom workingRoom) {
System.out.println("O> Wow, room #"+workingRoom.number);
}
public void visit(BossRoom bossRoom) {
System.out.println("O> Oh, look, this is "+bossRoom.bossName);
}
}
public class Main{
public static void main(String[] args) {
Office office = new Office(); // visited structure
// visitor who knows about office structure
Insider employee = new Insider();
office.accept(employee);
System.out.println();
// visitor who does not know about exact office structure
// but knows something else
Outsider candidate = new Outsider();
office.accept(candidate);
// no enumeration at all, but still a visitor pattern
Visitable v = new BossRoom("Linus Torvalds");
v.accept(candidate);
}
}
У меня был проект с широким использованием шаблона посетителя без какого-либо перечисления. У нас был базовый интерфейс Field и многие классы, реализующие его, например StringField, NumberField и т. Д. Очень часто нам приходилось делать разные вещи в зависимости от типа поля, например, отображать его другим способом, загружать из БД, экспортировать в xml и т. Д. Мы могли бы определять методы в интерфейсе Field, но это позволило бы объединить его с каждой отдельной особенностью проекта - бедное поле должно знать об экспорте, импорте, рендеринге в html и rtf и т. Д. Мы также могли бы использовать instanceof, но набор возможных классы, реализующие интерфейс поля, со временем менялись, и было возможно добавить новый тип поля и забыть добавить
else if (field instanceof NewlyAddedFieldType) {...}
где-то. Поэтому мы решили использовать шаблон посетителя, и это было похоже на
Visitor v = new XMLExportVisitor(outputStream);
field.accept(v);
Поскольку любая реализация поля должна иметь метод
void accept(FieldVisitor visitor)
тогда, если я добавлю новую реализацию интерфейса Field, я должен как-то реализовать ее. Обычно это
visitor.visit(this);
где это недавно добавленный класс. Это заставляет меня добавить
void visit(NewlyAddedClass visited);
к интерфейсу FieldVisitor, что позволяет мне применять его в каждой реализации FieldVisitor, которая у нас уже есть. Поэтому, если я забуду что-то сделать, я получу ошибку компилятора.
Перечисление в этом случае, если таковое имеется, было сделано за пределами посещенной структуры и посетителя. Но я все еще думаю об этом как о действительном случае шаблона посетителя.
Это оказалось немного сложнее в реализации, но проще и безопаснее в использовании.