Я регулярно использую шаблон посетителя в своем коде. Когда иерархия классов имеет посетителя, я использую его как альтернативу instanceof
и casting. Однако это приводит к некоторому довольно неудобному коду, который я хотел бы улучшить.
Рассмотрим надуманный случай:
interface Animal {
void accept(AnimalVisitor visitor);
}
class Dog implements Animal {
void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
class Cat implements Animal {
void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
interface AnimalVisitor {
default void visit(Cat cat) {};
default void visit(Dog dog) {};
}
В большинстве случаев, чтобы делать что-то конкретное только для собак (например), я реализую посетителя, который реализует логику в своем методе visit
- точно так же, как это предполагает шаблон.
Однако есть случай, когда я хочу вернуть дополнительную собаку от посетителя, чтобы использовать ее снаружи.
В этом случае я получаю довольно уродливый код:
List<Dog> dogs = new ArrayList<>();
animal.accept(new AnimalVisitor() {
void visit(Dog dog) {
dogs.add(dog);
}
}
Optional<Dog> possibleDog = dogs.stream().findAny();
Я не могу назначить possibleDog
Dog непосредственно внутри посетителя, потому что это не конечная переменная, следовательно, список.
Это довольно уродливое и неэффективное, просто для того, чтобы обойти требования для эффективной финальности. Меня интересовали бы идеи альтернатив.
Альтернативы, которые я рассмотрел:
Превращение посетителя в общий тип, которому может быть присвоено возвращаемое значение
interface Animal {
<T> T accept(AnimalVisitor<T> visitor);
}
interface AnimalVisitor <T> {
default Optional<T> visit(Dog dog) { return Optional.empty(); }
default Optional<T> visit(Cat cat) { return Optional.empty(); }
}
Создание абстрактного посетителя, который содержит большую часть кода и может быть тривиальным, чтобы установить необязательный напрямую
abstract class AnimalCollector implements AnimalVisitor <T> {
private Optional<T> result = Optional.empty;
protected void setResult(T value) {
assert !result.isPresent();
result = Optional.of(value);
}
public Optional<T> asOptional() {
return result;
}
}
Используйте построитель потоков вместо списка
Stream.Builder<Dog> dogs = Stream.builder();
animal.accept(new AnimalVisitor() {
void visit(Dog dog) {
dogs.accept(dog);
}
}
Optional<Dog> possibleDog = dogs.build().findAny();
Но я не считаю их особенно элегантными. Они включают в себя множество шаблонов только для реализации базовой логики asA
. Я использую второе решение в своем коде, чтобы сохранить его в чистоте. Есть ли более простое решение, которое мне не хватает?
Чтобы быть ясным, меня не интересуют ответы с некоторым вариантом "use instanceof and casts". Я понимаю, что это будет работать в этом тривиальном случае, но ситуации, которые я рассматриваю, имеют довольно сложное использование посетителей, которые включают в себя посещение композитов и делегатов, которые делают литье непрактичным.