java - 使用访问者模式而不是强制转换
问题描述
我经常在我的代码中使用访问者模式。当类层次结构实现了访问者时,我将其用作替代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
逻辑。我倾向于在我的代码中使用第二种解决方案来保持使用干净。我缺少一个更简单的解决方案吗?
为了清楚起见,我对“使用 instanceof 和 casts”的某些变体的答案不感兴趣。我意识到它可以在这种微不足道的情况下工作,但我正在考虑的情况对访问者的使用非常复杂,包括访问复合体和委托,这使得铸造不切实际。
解决方案
我知道您明确要求不使用的解决方案,instanceof
或者cast
实际上我认为在这种特殊情况下,您希望实现逻辑以过滤特定的子类型,这可能值得考虑,并且类似于您的泛型方法,恕我直言,这并不难看:
// as mentioned in the comment above I removed the Optional return types
interface AnimalVisitor<T> {
T visit(Dog dog);
T visit(Cat cat);
}
public class AnimalFinder<A extends Animal> implements AnimalVisitor<A> {
final Class<A> mAnimalClass;
public AnimalFinder(Class<A> aAnimalClass) {
this.mAnimalClass = aAnimalClass;
}
@Override
public A visit(Dog dog) {
if (dog != null && mAnimalClass.isAssignableFrom(dog.getClass())) {
return mAnimalClass.cast(dog);
} else {
return null;
}
}
@Override
public A visit(Cat cat) {
if (cat != null && mAnimalClass.isAssignableFrom(cat.getClass())) {
return mAnimalClass.cast(cat);
} else {
return null;
}
}
}
现在您可以简单地重用AnimalFinder
并提供您感兴趣的类型作为参数:
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
AnimalVisitor<Dog> dogFinder = new AnimalFinder<>(Dog.class);
System.out.println(Optional.ofNullable(dog.accept(dogFinder)));
System.out.println(Optional.ofNullable(cat.accept(dogFinder)));
// using AnimalFinder there is actually no need to implement something like DogPrinter
// simply use a Consumer or a lambda expression
Optional.ofNullable(dog.accept(dogFinder)).ifPresent(d -> System.out.println("Found dog" + d));
Optional.ofNullable(cat.accept(dogFinder)).ifPresent(d -> System.out.println("Found dog" + d));
}
从我的角度来看,这个解决方案有一些优点:
- 没有重复的代码来过滤代码中各处的不同动物类别
Animal
如果实现了新类型,则需要添加新访问方法的单个位置- 它易于使用且易于扩展(访问者模式并不总是如此)
莫里斯当然是对的。您可以简单地将AnimalFinder
访问者替换为一个简单的Predicate
(实际上几乎是GuavaPredicates.instanceOf
所做的):
public static <A, T> Predicate<A> instanceOf(final Class<T> aClass) {
return a -> (a != null && aClass.isAssignableFrom(a.getClass()));
}
并像这样使用它来过滤Optional
or Stream
:
System.out.println(Optional.ofNullable(dog).filter(instanceOf(Dog.class)));
这甚至更可重用(不仅限于Animal
s),重复的代码更少,并且可以在您获得Optional
or时使用Stream
。
推荐阅读
- javascript - 如何使用 3d 安全流在一张发票中处理产品和订阅?
- ios - iOS Swift - 崩溃:com.apple.NSURLSession-delegate EXC_BREAKPOINT 0x0000000102e35fc0
- javascript - 如何从arr返回数据
- sql - 如何查询表以获取当前总计数,包括以前的日期?
- java - 怎么理解`?扩展 T` 是协变的吗?
- stm32 - 嵌入式平台上是否有任何轻量级的 ftl lib 包含坏块管理和磨损均衡?
- sql-server - 为什么 TRY_PARSE 这么慢?
- visual-studio-code - 如何使vscode资源管理器中的雪佛龙不显示?
- android - Android Paging 3:LoadType.APPEND 返回空远程键
- flutter - Flutter GetX:未捕获(承诺中)错误:断言失败:_isComplete 不正确