首页 > 解决方案 > 使用访问者模式而不是强制转换

问题描述

我经常在我的代码中使用访问者模式。当类层次结构实现了访问者时,我将其用作替代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”的某些变体的答案不感兴趣。我意识到它可以在这种微不足道的情况下工作,但我正在考虑的情况对访问者的使用非常复杂,包括访问复合体和委托,这使得铸造不切实际。

标签: javadesign-patterns

解决方案


我知道您明确要求不使用的解决方案,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()));
}

并像这样使用它来过滤Optionalor Stream

System.out.println(Optional.ofNullable(dog).filter(instanceOf(Dog.class)));

这甚至更可重用(不仅限于Animals),重复的代码更少,并且可以在您获得Optionalor时使用Stream


推荐阅读