Я регулярно использую шаблон посетителя в своем коде.Когда в иерархии классов реализован посетитель, я использую его как альтернативу instanceof
и приведению.Однако это приводит к довольно неловкому коду, который я хотел бы улучшить.
Рассмотрим надуманный случай:
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
непосредственно внутри посетителя, потому что это не финальная переменная, поэтомусписок.
Это довольно уродливо и неэффективно, просто чтобы обойти требование эффективной финальности.Я был бы заинтересован в идеях альтернатив.
Альтернативы, которые я рассмотрел:
Превращение посетителя в универсальный, которому может быть возвращено значение
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".Я понимаю, что это сработает в этом тривиальном случае, но в рассматриваемых мной ситуациях достаточно сложное использование посетителей, включая посещение композитов и делегатов, что делает нецелесообразным кастинг.