这关于堆栈溢出的流行答案可以说明函数式编程和面向对象编程的区别:
当您对事物有固定的操作集时,面向对象的语言是很好的,并且随着代码的发展,您主要添加新的东西。这可以通过添加实现现有方法的新类来实现,而现有的类则不受影响。 当您有一组固定的事物时,函数式语言是很好的,并且随着代码的发展,您主要对现有的事物添加新的操作。这可以通过添加使用现有数据类型计算的新函数来实现,而现有的函数则不受影响。
假设我有一个Animal接口:
public interface Animal {
public void speak();
}我有一个Dog、Cat、Fish和Bird,它们都实现了接口。如果我想向Animal添加一个名为jump()的新方法,则必须遍历所有子类并实现jump()。
访问者模式可以缓解这个问题,但是使用Java 8中引入的新功能特性,我们应该能够以不同的方式解决这个问题。在scala中,我可以很容易地使用模式匹配,但是Java还没有真正做到这一点。
Java 8实际上是否使对现有事物添加新操作变得更容易?
发布于 2015-03-17 02:21:49
在大多数情况下,您试图完成的任务虽然令人钦佩,但并不适合Java。但在我开始之前..。
Java 8将默认方法添加到接口中!可以根据接口中的其他方法定义默认方法。这已经可以用于抽象类了。
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 shouldJava有一些工具可以使将方法添加到广泛的类集变得更容易。即抽象类和默认接口方法。Java的重点是基于调用方法的对象来调度方法。如果您想编写灵活的、性能良好的Java,我认为这是您必须采用的一个成语。
P.S.,因为我是那个Guy™,我要介绍Lisp,特别是通用Lisp对象系统(CLOS)。它提供了基于所有参数的多个分派方法。这本实用的通用Lisp甚至提供了这与Java有何不同?。
发布于 2015-03-17 21:01:25
对Java语言的添加并不会使每一个旧的概念都过时。实际上,访问者模式很好地支持添加新的操作。
将此模式与新的Java 8可能性进行比较时,可以看出以下几点:
Iterable.forEach、Stream.forEach和Stream.reduce处理平面同构集合时非常方便。因此,新的Java 8功能永远不能替代访问者模式,但是,寻找可能的协同作用是合理的。这个答案讨论了修改现有API (FileVisitor)以启用lambda表达式的可能性。该解决方案是一个专门的具体访问者实现,它将委托给相应的函数,这些函数可以为每个visit方法指定。如果每个函数都是可选的(即每个visit方法都有一个合理的缺省值),那么如果应用程序只对可能的操作的一小部分感兴趣,或者它想要一致地处理其中的大多数操作,就会派上用场。
如果其中一些用例被认为是“典型的”,则可能有一个accept方法使用一个或多个函数来创建场景后面的适当的委托访问者(当设计新的API或在您的控制下改进API时)。但是,我不会放弃普通的accept(XyzVisitor),因为使用访问者的现有实现的选项不应该被低估。
如果我们将Stream看作是Stream的访问者,那么在Stream API中也有类似的重载选择。它由最多四个功能组成,这是访问一个平面的、同质的项目序列所能想到的最大功能。不必实现该接口,您可以启动一个指定单个函数的约简或使用三种函数的可变约简,但是在一些常见的情况下,指定现有实现比通过lambda表达式/方法引用指定所有必需的函数更简洁,比如collect(Collectors.toList())或collect(Collectors.joining(","))。
当向访问者模式的特定应用程序添加这种支持时,它将使调用站点更加闪亮,而特定accept方法的实现站点总是很简单。因此,唯一仍然庞大的部分是访问者类型本身;如果增加了对基于功能的接口操作的支持,它甚至可能变得更加复杂。在不久的将来,不太可能有一种基于语言的解决方案,可以更简单地创建这样的访问者,也不可能取代这个概念。
发布于 2015-03-17 02:42:07
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
);
}https://stackoverflow.com/questions/29090179
复制相似问题