This受欢迎的答案:
当你有一套固定的语言时,面向对象语言是很好的 上的操作,随着代码的发展,您主要添加新内容 的东西。这可以通过添加实现的新类来实现 现有的方法,现有的课程都不在考虑范围内。
当你拥有一套固定的东西时,功能语言会很好 随着代码的发展,您主要在现有代码上添加新的操作 的东西。这可以通过添加计算的新函数来实现 使用现有数据类型,现有功能不受影响。
说我有一个Animal
界面:
public interface Animal {
public void speak();
}
我有一个Dog
,Cat
,Fish
和Bird
都可以实现接口。如果我想向名为Animal
的{{1}}添加新方法,我必须遍历所有子类并实现jump()
。
访问者模式可以缓解这个问题,但似乎随着Java 8中引入的新功能特性,我们应该能够以不同的方式解决这个问题。在jump()
我可以很容易地使用模式匹配,但Java还没有真正拥有它。
Java 8是否真的能够更轻松地在现有内容上添加新的操作?
答案 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可能性进行比较时,以下内容变得显而易见:
Iterable.forEach
,Stream.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 function或mutable 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
);
}