使用访客模式而不是强制转换

时间:2018-05-25 21:02:18

标签: java design-patterns

我经常在我的代码中使用访问者模式。当类层次结构实现了访问者时,我将其用作instanceof和cast的替代方法。然而,它导致了一些非常尴尬的代码,我想改进它。

考虑一下人为的案例:

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逻辑。我倾向于在我的代码中使用第二个解决方案来保持使用清洁。我错过了一个更简单的解决方案吗?

为了清楚起见,我对#34;使用instanceof和casts&#34;的某些变体的答案不感兴趣。我意识到它可以在这个微不足道的情况下工作,但我考虑的情况对访问者的使用相当复杂,包括访问复合材料和代表,这使得投射不切实际。

7 个答案:

答案 0 :(得分:1)

通过将问题中的AnimalCollector分成两部分,我认为我们可以创建出非常简洁的内容。

(这些示例基于问题中原始的AnimalVisitor接口-使用诸如void visit(Cat cat);这样的方法)。

“收集到可选”部分可以很好地提取到独立类中:

public class OptionalCollector<T> {
    private Optional<T> result = Optional.empty();

    public void setResult(T value) {
        result = Optional.of(value);
    }

    public Optional<T> asOptional() {
        return result;
    }
}

不过,另一种可能性是有点“ lambda访问”样式编码。一些静态工厂方法使访问者可以轻松定义,而无需在匿名内部类中声明方法。

这些是一些示例工厂方法:

import java.util.function.Consumer;

public class AnimalVisitorFactory {
    static AnimalVisitor dogVisitor(Consumer<Dog> dogVisitor) {
        return new AnimalVisitor() {
            @Override
            public void visit(Dog dog) {
                dogVisitor.accept(dog);
            }
        };
    }

    static AnimalVisitor catVisitor(Consumer<Cat> catVisitor) {
        return new AnimalVisitor() {
            @Override
            public void visit(Cat cat) {
                catVisitor.accept(cat);
            }
        };
    }
}

然后可以将这两个部分结合起来:

import static AnimalVisitorFactory.*;

OptionalCollector<Dog> collector = new OptionalCollector<>();
animal.accept(dogVisitor(dog -> collector.setResult(dog)));
Optional<Dog> possibleDog = collector.asOptional();

这超出了问题中案例所需要的范围,但是请注意,可以使用流利的API风格进一步完善该思想。在dogVisitor()接口上也使用类似的catVisitor()AnimalVisitor默认方法,然后可以将几个lambda链接在一起以构建更完整的访问者。

答案 1 :(得分:1)

我知道您明确要求不使用instanceofcast的解决方案,但实际上我认为在这种特殊情况下,您想实现逻辑以过滤特定子类型,这可能是值得的考虑并类似于您的仿制药方法,这并不难看恕我直言:

// 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访问者(实际上几乎是Guava's Predicates.instanceOf的作用):

public static <A, T> Predicate<A> instanceOf(final Class<T> aClass) {
    return a -> (a != null && aClass.isAssignableFrom(a.getClass()));
}

并像这样使用它来过滤OptionalStream

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

这甚至具有更高的可重用性(不仅限于Animal,重复代码更少,只要您拥有OptionalStream,就可以使用。

答案 2 :(得分:1)

在这种情况下,我认为您不需要访客;它使代码变得多余。我宁愿使用选择器:

public class AnimalSelector<T extends Animal> {
    private final Class<T> clazz;

    public AnimalSelector(Class<T> clazz) {
        this.clazz = clazz;
    }

    public T select(Animal a) {
        if (clazz.isInstance(a)) {
            return clazz.cast(a);
        } else {
            return null;
        }
    }
}

现在您可以编写如下内容:

Dog dog = new AnimalSelector<>(Dog.class).select(animal);
Cat cat = new AnimalSelector<>(Cat.class).select(animal);

答案 3 :(得分:1)

实际上,您不需要返回虚空的AnimalVisitor。根据您的代码并维持Visitor模式,我将通过以下方式进行操作。

动物界面。

public interface Animal {

    default <T extends Animal> Stream<T> accept(AnimalVisitor visitor) {
        try {
            return visitor.visit(this).stream();
        } catch (ClassCastException ex) {
            return Stream.empty();
        }
    }
}

猫狗派生类。

public class Dog implements Animal {

    @Override
    public String toString() {
        return "Fido";
    }
}

public class Cat implements Animal {

    @Override
    public String toString() {
        return "Felix";
    }
}

AnimalVisitor界面。

public interface AnimalVisitor<T extends Animal> {

    Optional<T> visit(T animal);
}

将它们放在一起。

public class AnimalFinder {

    public static void main(String[] args) {

        Animal dog = new Dog();
        Animal cat = new Cat();

        /*
         * The default/old way
         * AnimalVisitor<Dog> dogFinder = new AnimalVisitor<Dog>() {
         *    @Override
         *    public Optional<Dog> visit(Dog animal) {
         *        return Optional.of(animal);
         *    }
         * };
         *
         * Or lambda expression
         *
         * AnimalVisitor<Dog> dogFinder = (animal) -> Optional.of(animal);
         *
         * Or member reference
         */
        AnimalVisitor<Dog> dogFinder = Optional::of;

        Optional fido = dog.accept(dogFinder).findAny();
        Optional felix = cat.accept(dogFinder).findAny();

        System.out.println(fido); // Optional[Fido]
        System.out.println(felix); // Optional.empty

        felix.ifPresent(a -> System.out.printf("Found %s\n", a));
        fido.ifPresent(a -> System.out.printf("Found %s\n", a)); // Found Fido
    }
}

答案 4 :(得分:0)

我试用了通用访客解决方案。它实际上并不太糟糕 - 主要的丑陋是当访问者不需要返回值时使用Optional<Void>

我仍然对任何更好的选择感兴趣。

public class Visitor {
    interface Animal {
        <T> Stream<T> accept(AnimalVisitor<T> visitor);
    }

    static class Dog implements Animal {
        @Override
        public String toString() {
            return "Fido";
        }

        @Override
        public <T> Stream<T> accept(AnimalVisitor<T> visitor) {
            return visitor.visit(this).stream();
        }
    }

    static class Cat implements Animal {
        @Override
        public String toString() {
            return "Felix";
        }

        @Override
        public <T> Stream<T> accept(AnimalVisitor<T> visitor) {
            return visitor.visit(this).stream();
        }
    }

    interface AnimalVisitor<T> {
        default Optional<T> visit(Dog dog) {
            return Optional.empty();
        }

        default Optional<T> visit(Cat cat) {
            return Optional.empty();
        }
    }

    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();
        AnimalVisitor<Dog> dogFinder = new AnimalVisitor<Dog>() {
            @Override
            public Optional<Dog> visit(Dog dog) {
                return Optional.of(dog);
            }
        };
        AnimalVisitor<Void> dogPrinter = new AnimalVisitor<Void>() {
            @Override
            public Optional<Void> visit(Dog dog) {
                System.out.println("Found dog " + dog);
                return Optional.empty();
            }
        };
        System.out.println(dog.accept(dogFinder).findAny());
        System.out.println(cat.accept(dogFinder).findAny());
        dog.accept(dogPrinter);
        cat.accept(dogPrinter);
    }
}

答案 5 :(得分:0)

如何?

interface Animal {
    <T> T accept(AnimalVisitor<T> visitor);
}

static class Dog implements Animal {
    public <T> T accept(AnimalVisitor<T> visitor) {
        return visitor.visit(this);
    }
}

static class Cat implements Animal {
    public <T> T accept(AnimalVisitor<T> visitor) {
        return visitor.visit(this);
    }
}

interface AnimalVisitor<T> {
    default T defaultReturn(){ return null; }
    default T visit(Cat cat) { return defaultReturn(); };
    default T visit(Dog dog) { return defaultReturn(); };
    interface Finder<T> extends AnimalVisitor<Optional<T>> {
        @Override default Optional<T> defaultReturn(){ return Optional.empty(); }
    }
    interface CatFinder extends Finder<Cat> {
        @Override default Optional<Cat> visit(Cat cat){
            return Optional.ofNullable(find(cat));
        }
        Cat find(Cat cat);
        CatFinder FIND = c -> c;
    }
    interface DogFinder extends Finder<Dog> {
        @Override default Optional<Dog> visit(Dog dog){
            return Optional.ofNullable(find(dog));
        }
        Dog find(Dog dog);
        DogFinder FIND = d -> d;
    }
}

然后使用它...

Animal a = new Cat();
Optional<Cat> o = a.accept((CatFinder)c -> {
    //use cat
    return c; //or null to get an empty optional;
});

//or if you just want to always return the cat:
Optional<Cat> o = a.accept(CatFinder.FIND);

如果需要,可以更改find以返回布尔值,因此它就像一个谓词。

答案 6 :(得分:0)

我会尝试使用由访问者管理的侦听器来分解逻辑。当访客找到一条狗时,它想归还它,它会呼叫所有注册的侦听器来报告该狗。反过来,听众将负责对要报告的狗进行任何您想做的事情。通过将报告的狗聚合逻辑移出访客实现,这可以使实现更整洁。