Java 8是否提供了访问者模式的替代方案?

时间:2015-03-17 02:09:17

标签: java oop functional-programming java-8

有关函数式编程和面向对象编程之间差异的

This受欢迎的答案:

  

当你有一套固定的语言时,面向对象语言是很好的    上的操作,随着代码的发展,您主要添加新内容   的东西。这可以通过添加实现的新类来实现   现有的方法,现有的课程都不在考虑范围内。

     

当你拥有一套固定的东西时,功能语言会很好   随着代码的发展,您主要在现有代码上添加新的操作   的东西。这可以通过添加计算的新函数来实现   使用现有数据类型,现有功能不受影响。

说我有一个Animal界面:

public interface Animal {
    public void speak();
}

我有一个DogCatFishBird都可以实现接口。如果我想向名为Animal的{​​{1}}添加新方法,我必须遍历所有子类并实现jump()

访问者模式可以缓解这个问题,但似乎随着Java 8中引入的新功能特性,我们应该能够以不同的方式解决这个问题。在jump()我可以很容易地使用模式匹配,但Java还没有真正拥有它。

Java 8是否真的能够更轻松地在现有内容上添加新的操作

3 个答案:

答案 0 :(得分:10)

在大多数情况下,您尝试完成的内容虽然令人钦佩,但并不适合Java。但在我进入之前......

Java 8为接口添加了默认方法!您可以根据界面中的其他方法定义默认方法。这已经可用于抽象类。

public interface Animal {
    public void speak();
    public default void jump() {
        speak();
        System.out.println("...but higher!");
    }
}

但最终,您必须为每种类型提供功能。我没有看到添加新方法和创建访问者类或部分功能之间的巨大差异。这只是一个位置问题。您想通过操作或对象组织代码吗? (功能或面向对象,动词或名词等)

我想我想说的是,Java代码是由' noun'组织的。由于不会很快改变的原因。

访问者模式以及静态方法可能是您通过行动组织事物的最佳选择。但是,我认为当访客真正依赖于他们访问的对象的确切类型时,他们会发挥最大作用。例如,动物访客可能会被用来使动物说话然后跳跃,因为所有动物都支持这些动物。跳跃的访客对我来说并没有多大意义,因为这种行为本质上是针对每只动物的。

Java创造了一个真实的"动词"接近一点困难,因为它根据参数的编译时类型选择运行哪个重载方法(参见下面的Overloaded method selection based on the parameter's real type)。仅根据this的类型动态调度方法。这是继承原因之一,是处理这些类型情况的首选方法。

public class AnimalActions {
    public static void jump(Animal a) {
        a.speak();
        System.out.println("...but higher!");
    }
    public static void jump(Bird b) { ... }
    public static void jump(Cat c) { ... }
    // ...
}
// ...
Animal a = new Cat();
AnimalActions.jump(a); // this will call AnimalActions.jump(Animal)
                       // because the type of `a` is just Animal at
                       // compile time.

您可以使用instanceof和其他形式的反思来解决这个问题。

public class AnimalActions {
    public static void jump(Animal a) {
        if (a instanceof Bird) {
            Bird b = (Bird)a;
            // ...
        } else if (a instanceof Cat) {
            Cat c = (Cat)a;
            // ...
        }
        // ...
    }
}

但是现在你只是在做JVM旨在为你做的工作。

Animal a = new Cat();
a.jump(); // jumps as a cat should

Java提供了一些工具,可以更轻松地向一组广泛的类添加方法。即抽象类和默认接口方法。 Java专注于基于调用该方法的对象调度方法。如果你想编写灵活且高性能的Java,我认为这是你必须采用的一个习惯用法。

P.S。因为我那个家伙™我将提出Lisp,特别是Common Lisp对象系统(CLOS)。它提供了基于所有参数进行调度的多方法。本书 Practical Common Lisp 甚至提供了an example of how this differs from Java

答案 1 :(得分:6)

对Java语言的添加不会使每个旧概念过时。事实上,访客模式非常擅长支持添加新操作。

将此模式与新的Java 8可能性进行比较时,以下内容变得显而易见:

  • Java 8允许轻松定义包含单个函数的操作。在使用Iterable.forEachStream.forEach以及Stream.reduce
  • 处理扁平同类集合时,此功能非常方便
  • 访问者允许定义多个函数,这些函数由数据结构的元素类型和/或拓扑结构选择,当单个函数功能停止工作时,处理异构集合时非常有趣 - 平面结构,例如物品树

因此,新的Java 8功能永远不会成为访问者模式的替代品,但是,寻找可能的协同效应是合理的。 This answer讨论了改进现有API(FileVisitor)以启用lambda表达式的可能性。该解决方案是一个专门的具体访问者实现,它委托给可以为每个visit方法指定的相应函数。如果每个函数都是可选的(即每个visit方法都有一个合理的默认值),如果应用程序只对一小部分可能的操作感兴趣,或者它想要统一处理它们中的大部分,它就会派上用场

如果其中一些用例被视为“典型”,则可能有accept方法采用一个或多个函数在场景后创建适当的委托访问者(在设计新API或改进API时) 。但是,我不会放弃普通accept(XyzVisitor),因为不应低估使用现有访问者实现的选项。

如果我们将Collector视为Stream的访问者,Stream API中的重载选择类似。它由最多四个函数组成,这是访问平坦,同类的项目序列所能想到的最大值。您可以发起reduction specifying a single functionmutable reduction using three functions,而不是必须实现该界面,但有一些常见情况specifying an existing implementation更简洁,例如collect(Collectors.toList())或{{1}通过lambda表达式/方法引用指定所有必需的函数。

当将这种支持添加到访问者模式的特定应用程序时,它将使调用站点更加闪亮,而特定collect(Collectors.joining(","))方法的实现站点总是很简单。因此,唯一保持笨重的部分是访客类型本身;当它增加了对基于功能接口的操作的支持时,它甚至可能变得有点复杂。在不久的将来,不太可能有基于语言的解决方案,更简单地创建此类访问者或替换此概念。

答案 2 :(得分:5)

Lambda表达式可以更容易地设置(非常)穷人的模式匹配。可以使用相同的技术使访问者更容易构建。

static interface Animal {
    // can also make it a default method 
    // to avoid having to pass animal as an explicit parameter
    static void match(
            Animal animal,
            Consumer<Dog> dogAction,
            Consumer<Cat> catAction,
            Consumer<Fish> fishAction,
            Consumer<Bird> birdAction
    ) {
        if (animal instanceof Cat) {
            catAction.accept((Cat) animal);
        } else if (animal instanceof Dog) {
            dogAction.accept((Dog) animal);
        } else if (animal instanceof Fish) {
            fishAction.accept((Fish) animal);
        } else if (animal instanceof Bird) {
            birdAction.accept((Bird) animal);
        } else {
            throw new AssertionError(animal.getClass());
        }
    }
}

static void jump(Animal animal) {
    Animal.match(animal,
            Dog::hop,
            Cat::leap,
            fish -> {
                if (fish.canJump()) {
                    fish.jump();
                } else {
                    fish.swim();
                }
            },
            Bird::soar
    );
}