Java8是否提供了访问者模式的替代方案?
关于堆栈溢出的流行答案是关于函数式编程和面向对象编程之间的区别: 当您有一组固定的 操作,并且随着代码的发展,您主要添加新的 东西。这可以通过添加实现 现有的方法和现有的类被单独保留 当你有一套固定的东西时,函数式语言是很好的,而且 随着代码的发展,您主要会在现有的基础上添加新的操作 东西。这可以通过添加新的计算 使用现有的数据类型,而不使用现有的函数 假设我有一个Java8是否提供了访问者模式的替代方案?,java,oop,functional-programming,java-8,Java,Oop,Functional Programming,Java 8,关于堆栈溢出的流行答案是关于函数式编程和面向对象编程之间的区别: 当您有一组固定的 操作,并且随着代码的发展,您主要添加新的 东西。这可以通过添加实现 现有的方法和现有的类被单独保留 当你有一套固定的东西时,函数式语言是很好的,而且 随着代码的发展,您主要会在现有的基础上添加新的操作 东西。这可以通过添加新的计算 使用现有的数据类型,而不使用现有的函数 假设我有一个Animal界面: public interface Animal { public void speak(); } 我有
Animal
界面:
public interface Animal {
public void speak();
}
我有一只狗
,猫
,鱼
,还有鸟
,它们都实现了接口。如果我想向Animal
添加一个名为jump()
的新方法,我必须遍历所有子类并实现jump()
访问者模式可以缓解这个问题,但是通过Java8中引入的新功能特性,我们应该能够以不同的方式解决这个问题。在scala中,我可以很容易地使用模式匹配,但Java还没有
Java 8真的使在现有事物上添加新操作变得更容易了吗?您试图完成的虽然令人钦佩,但在大多数情况下并不适合Java。但在我开始之前 Java8为接口添加了默认方法!可以基于接口中的其他方法定义默认方法。这已经适用于抽象类
public interface Animal {
public void speak();
public default void jump() {
speak();
System.out.println("...but higher!");
}
}
但最终,您必须为每种类型提供功能。我看不出添加新方法和创建访问者类或部分函数之间有多大区别。这只是位置的问题。是否要按操作或对象组织代码?(功能或面向对象、动词或名词等)
我想我要说的一点是,Java代码是按“名词”组织的,其原因不会很快改变
访问者模式和静态方法可能是通过操作组织事物的最佳选择。然而,我认为当访问者并不真正依赖于他们正在访问的对象的确切类型时,他们才是最有意义的。例如,一个动物访客可能被用来让动物说话然后跳跃,因为所有动物都支持这两种东西。一个跳跃访客对我来说没有多大意义,因为这种行为天生就是每种动物特有的
Java使真正的“动词”方法有点困难,因为它根据参数的编译时类型选择要运行的重载方法(见下文和)。方法仅根据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,我认为这是您必须采用的一种习惯用法
因为我就是那个家伙™ 我将介绍Lisp,特别是公共Lisp对象系统(CLOS)。它提供了基于所有参数进行调度的多方法。这本书甚至提供了实用的通用Lisp。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
);
}
静态界面动物{
//也可以将其作为默认方法
//避免将animal作为显式参数传递
静态空洞匹配(
动物,
消费者行动,
消费者行为,
消费者渔业行动,
消费者鸟语
) {
if(猫的动物实例){
接受((猫)动物);
}else if(狗的动物实例){
接受((狗)动物);
}else if(鱼的动物实例){
接受((鱼)动物);
}else if(鸟类的动物实例){
接受(鸟)动物;
}否则{
抛出新的断言错误(animal.getClass());
}
}
}
静态空跳(动物){
动物。匹配(动物,
狗跳,
猫:跳跃,
鱼->{
if(fish.canJump()){
鱼。跳();
}否则{
鱼。游泳();
}
},
飞翔
);
}
对Java语言的添加并不会使所有旧概念都过时。事实上,访问者模式非常擅长支持添加新操作
将此模式与新的Java 8可能性进行比较时,以下内容变得显而易见:
- Java8允许轻松定义包含单个函数的操作。这在处理平面同质集合时非常方便,例如
- 访问者可以定义多个功能,这些功能由数据结构的元素类型和/或拓扑选择,在处理异构集合和非平面结构(如项目树)时,在单个功能功能停止工作的地方,这些功能变得有趣