Does Java 8 provide an alternative to the visitor pattern?
这个关于堆栈溢出的流行答案可以说明函数编程和面向对象编程之间的区别:
Object-oriented languages are good when you have a fixed set of
operations on things, and as your code evolves, you primarily add new
things. This can be accomplished by adding new classes which implement
existing methods, and the existing classes are left alone.Functional languages are good when you have a fixed set of things, and
as your code evolves, you primarily add new operations on existing
things. This can be accomplished by adding new functions which compute
with existing data types, and the existing functions are left alone.
假设我有一个
1 2 3 | public interface Animal { public void speak(); } |
我有一个
访问者模式可以减轻这个问题,但是似乎在Java 8中引入了新的功能特性,我们应该能够以不同的方式解决这个问题。在EDCOX1,8中,我可以很容易地使用模式匹配,但是Java还没有真正的模式匹配。
Java 8实际上是否更容易在现有的事情上添加新操作?
在大多数情况下,你想达到的目标是令人钦佩的,但不适合Java。但在我开始之前…
Java 8向接口添加默认方法!可以基于接口中的其他方法定义默认方法。这已可用于抽象类。
1 2 3 4 5 6 7 | public interface Animal { public void speak(); public default void jump() { speak(); System.out.println("...but higher!"); } } |
但最后,您必须为每种类型提供功能。我看不出在添加新方法和创建访问者类或部分函数之间有什么巨大的区别。只是位置问题。是否按操作或对象组织代码?(功能或面向对象、动词或名词等)
我想我要说的是,Java代码是由"名词"组织的,原因是不会很快改变。
访问者模式和静态方法可能是按操作组织事物的最佳选择。然而,我认为当游客不真正依赖他们所参观的对象的确切类型时,他们是最有意义的。例如,一个动物访客可能被用来让动物说话,然后跳,因为这两种东西都是由所有动物支持的。跳跃游客对我来说没什么意义,因为这种行为对每只动物都是固有的。
Java使一个真正的"动词"方法有点困难,因为它根据参数的编译时类型选择了重载方法来运行(参见下面的参数和基于参数的实际类型的重载方法选择)。方法仅根据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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. |
你可以通过使用
1 2 3 4 5 6 7 8 9 10 11 12 | 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为你设计的工作。
1 2 | Animal a = new Cat(); a.jump(); // jumps as a cat should |
Java有一些工具可以使方法更容易地添加到一组广泛的类中。即抽象类和默认接口方法。Java专注于基于调用该方法的对象的调度方法。如果你想编写灵活和性能好的Java,我认为这是你必须采用的一个成语。
因为我就是那个人?我将介绍Lisp,特别是通用Lisp对象系统(CLOS)。它提供了基于所有参数进行调度的多方法。这本书实用的通用LISP甚至提供了一个例子,说明了它与Java的区别。
对Java语言的添加不会使每个旧概念过时。实际上,访问者模式非常擅长支持添加新操作。
当将这种模式与新的Java和8种可能性进行比较时,下面变得显而易见:
- Java&Nbsp 8允许容易地定义包括单个函数的操作。这在处理平面齐次集合时非常方便,例如使用
Iterable.forEach 、Stream.forEach 以及Stream.reduce 。 - 访问者允许定义一个由数据结构的元素类型和/或拓扑选择的多个函数,当处理异类集合和非平面结构(例如项目树)时,在单个函数功能停止工作的地方,它变得有趣。
因此,新的Java&Nbsp 8功能不能作为访问者模式替换的下降,然而,寻找可能的协同作用是合理的。此答案讨论了对现有API(
如果其中一些用例被认为是"典型的",那么可能有一个
如果我们将
当对访问者模式的特定应用程序添加这样的支持时,它将使调用站点更加闪亮,而特定
lambda表达式可以更容易地建立(非常)差的人的模式匹配。同样的技术也可以用来让访问者更容易构建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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 ); } |