学习Java函数式编程(六):重构设计模式

本篇将讨论用Java 8函数式编程的方式重构设计模式。

重构策略模式

什么是策略模式可以查看本站以往的文章策略模式,接下来将重构那篇文章中的代码。

在那篇文章中,策略是针对选橘子(Orange)的喜好(UniqueHobby)而制定的,选Kind1调用方法将打印“红色水果”,选Kind2将打印”黄色水果”。Orange的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class Fruit {
UniqueHobby uniqueH;
public abstract void cleanliness();
public void performMethod() {
uniqueH.uniqueHobby();
}
}
class Orange extends Fruit {
public Orange (UniqueHobby uh) {
uniqueH = uh;
}
public void cleanliness() {
System.out.println("水果已被服务人员清洗干净");
}
}

构造器传递一个UniqueHobby的实例,通过调用Orange父类的performMethod方法,执行UniqueHobby实例的方法。

那么如何使用Lambda表达式重构呢?

直接比较main方法内调用的情况:

1
2
3
4
5
6
7
8
Fruit o1 = new Orange(new Kind1());
Fruit o2 = new Orange(new Kind2());
o1.cleanliness();
o1.performMethod();
o2.performMethod();
Fruit o3 = new Orange(() -> System.out.println("绿色水果"));
o3.performMethod();

o1和o2是没重构之前的策略模式产生的策略,而在重构的代码中直接传入了Lambda表达式,原理就在于,这里将UniqueHobby当作一个函数式接口,通过实现uniqueHobby方法实现策略。

《Java 8实战》强烈推荐使用Lambda表达式来解决这类问题。

重构观察者模式

什么是观察者模式可以查看本站以往的文章观察者模式,接下来将重构那篇文章中的代码。

在书中,Lambda运用在主题注册观察者的行为上,结合本人自己的例子,也就是将订阅者的接口当作函数式接口:

1
2
3
interface C {
public void update(String when, String where, String what);
}

接下来直接在main方法添加代码:

1
2
3
4
5
P specP = new ImlP();
C specC = new ImlC(specP);
specP.registerC((x, y, z) ->
System.out.println("时间:" + x + "\n地点:" + y + "\n事件:" + z));
((ImlP) specP).reSetData("早上十点", "火星", "人类探测器着陆");

未重构之前的代码将注册、修改、打印输出分别放在构造方法和两个重写的方法中,而在这里通过一行代码完成了这些逻辑。

重构职责链模式

什么是职责链模式可以查看本站以往的文章职责链模式,接下来将重构那篇文章中的代码。

未重构之前,构造了一个SendToMember抽象类,它不仅抽象了厌食、偏食以及通食这三类人群,还提供了职责传递的方法sendToMember(),接下来的重构过程中,将摒弃该类。

首先采用一个一元运算接口UnaryOperator创建childHandle:

1
UnaryOperator<Thing01> childHandle = x -> x;

这就好比一个管道,管道这边传入产品,从那头输出产品,只不过这里的代码更关注产品怎么“流通”,而不是专注于管道怎么搭建。这里的“x”就是指代买来全家享用的产品。由于小孩”厌食”,食品没有在小孩这关留下“痕迹”。

然后是母亲这关:

1
2
3
4
5
6
7
UnaryOperator<Thing01> mumHandle = x -> {
if (x != null && x.getF().getColor().equals("red")) {
System.out.println("被Mum吃掉");
return null;
}
return x;
};

母亲是挑食者,所以在方法体中添加上if语句,这里没有经过任何柯里化处理,但相较未重构前,逻辑非常清晰,一旦食品被母亲吃掉,将返回null,整个流程就是食物“悲惨一生”的过程。

最后是父亲:

1
2
3
4
5
6
7
UnaryOperator<Thing01> fatherHandle = x -> {
if (x != null) {
System.out.println("被father吃掉");
return null;
}
return x;
};

只要之前食品没有被吃掉,那么就“吃掉”然后返回null,父亲是最后的“光盘”捍卫者。

最后用andThen将各部分串联成流水线:

1
2
3
Function<Thing01, Thing01> pipline =
childHandle.andThen(mumHandle).andThen(fatherHandle);
Thing01 last = pipline.apply(new Thing01(new Apple00()));

这里要小心每个环节的输入与输出,andTen()是Function提供的一个默认方法,Function还提供了另一个默认方法compose(),这两者执行顺序相反。

模板方法 & 工厂模式

模板方法和环绕执行的思路是相似的,而工厂模式的重构借助了方法引用。比如:

1
2
Supplier<Product> loanSupplier = Loan::new;
Loan loan = loanSupplier.get();

这里借用了书上的例子,只是为了做一个基本的样例,理解起来相比前面介绍的重构方法更加简单。

小结

本文旨在举例说明用Lambda表达式重构传统设计模式的一般方法。总体而言,Lambda表达式写出来的代码更清晰更易读也更简洁。

参考

如何正确地使用设计模式?