Cor*_*ein 13 java oop functional-programming java-8
这个关于Stack Overflow的流行答案可以说明函数式编程和面向对象编程之间的区别:
当你对事物有一套固定的操作时,面向对象的语言是很好的 ,随着代码的发展,你主要添加新东西.这可以通过添加实现现有方法的新类来完成,并且现有类保持不变.
当你有一套固定的东西时,函数式语言是很好的,随着代码的发展,你主要在现有的东西上添加新的操作.这可以通过添加使用现有数据类型计算的新函数来实现,并且现有函数是独立的.
说我有一个Animal界面:
public interface Animal {
public void speak();
}
Run Code Online (Sandbox Code Playgroud)
我有一个Dog,Cat,Fish和Bird所有实现的接口.如果我想为Animalnamed 添加一个新方法jump(),我将不得不浏览所有子类并实现jump().
访问者模式可以缓解这个问题,但似乎随着Java 8中引入的新功能特性,我们应该能够以不同的方式解决这个问题.在scala我可以很容易地使用模式匹配,但Java还没有真正拥有它.
Java 8实际上是否更容易在现有事物上添加新操作?
axb*_*unt 10
在大多数情况下,您想要实现的目标虽然令人钦佩,但并不适合Java.但在我进入之前......
Java 8为接口添加了默认方法!您可以根据界面中的其他方法定义默认方法.这已经可用于抽象类.
public interface Animal {
public void speak();
public default void jump() {
speak();
System.out.println("...but higher!");
}
}
Run Code Online (Sandbox Code Playgroud)
但最终,您将不得不为每种类型提供功能.我没有看到添加新方法和创建访问者类或部分功能之间的巨大差异.这只是一个位置问题.您想通过操作或对象组织代码吗?(功能或面向对象,动词或名词等)
我想我要说的是,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.
Run Code Online (Sandbox Code Playgroud)
你可以通过使用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;
// ...
}
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
但现在你正在做的工作是JVM旨在为你做的.
Animal a = new Cat();
a.jump(); // jumps as a cat should
Run Code Online (Sandbox Code Playgroud)
Java有一些工具可以更容易地为一组广泛的类添加方法.即抽象类和默认接口方法.Java专注于基于调用该方法的对象调度方法.如果你想编写灵活且高性能的Java,我认为这是你必须采用的一个习惯用法.
PS因为我是That Guy ™我将提出Lisp,特别是Common Lisp对象系统(CLOS).它提供了基于所有参数进行调度的多方法.实用Common Lisp一书甚至提供了一个与Java不同的例子.
对Java语言的添加并没有使每个旧概念过时.事实上,访客模式非常擅长支持添加新操作.
将此模式与新的Java 8可能性进行比较时,以下内容变得明显:
Iterable.forEach,Stream.forEach但也可以Stream.reduce因此,新的Java 8功能永远不会成为访问者模式的替代品,但是,寻找可能的协同效应是合理的.这个答案讨论了改进现有API(FileVisitor)以支持使用lambda表达式的可能性.该解决方案是一个专门的具体访问者实现,它委托给可以为每个visit方法指定的相应函数.如果每个函数都是可选的(即每个visit方法都有一个合理的默认值),如果应用程序仅对可能的操作的一小部分感兴趣或者如果它想要统一处理它们中的大多数,它将会派上用场.
如果这些用例中的一些被视为"典型",则可能有一种accept方法采用一个或多个函数在场景后创建适当的委托访问者(在设计新API或在您的控制下改进API时).accept(XyzVisitor)但是,我不会放弃普通,因为不应低估使用现有访客实施的选项.
Stream如果我们认为a Collector作为一种访问者,那么API中有类似的重载选择Stream.它由最多四个函数组成,这是访问平坦,同类的项目序列所能想到的最大值.您可以使用三个函数启动减少,指定单个函数或可变减少,而不是必须实现该接口,但是通常情况下,指定现有实现更简洁,比如使用collect(Collectors.toList())或collect(Collectors.joining(","))通过lambda表达式指定所有必需的函数/方法参考.
将这种支持添加到访问者模式的特定应用程序时,它将使调用站点更加闪亮,而特定accept方法的实现站点总是很简单.因此,唯一保持笨重的部分是访客类型本身; 当它增加了对基于功能接口的操作的支持时,它甚至可能变得有点复杂.在不久的将来,不太可能存在基于语言的解决方案,更简单地创建此类访问者或替换此概念.
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
);
}
Run Code Online (Sandbox Code Playgroud)