Java函数式编程(五)设计和架构的原则
目录
3 函数式编程特性的应用
3.4 设计和架构的原则
3.4.1 使用Lambda表达式的SOLID原则
Single responsibility - 单一职责
Open/closed - 开闭原则
Liskov subsitiution - 里氏替换
Interface segregation - 接口隔离
Dependency inversion - 依赖到职
3.4.1.1 单一职责原则
程序中的类或方法只能由一个改变的理由。
我们来看一个例子,求1-100之间的素数
package com.qupeng.fp.design.principle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SingleResponsibility {
public static void main(String[] args) {
// 输出:[1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
System.out.println(collectPrimesEntirety(100));
}
public static List<Long> collectPrimesEntirety(int upTo) {
List<Long> primes = new ArrayList<>();
for (long i = 1; i <= upTo; i++) {
boolean isPrime = true;
for (long j = 2; j < i; j++) {
if (0 == i % j) {
isPrime = false;
break;
}
}
if (isPrime) {
primes.add(i);
}
}
return Collections.unmodifiableList(primes);
}
}
程序运行没有任何问题,但是它违反了单一职责原则。这段代码有两个关键的职责:
- 收集指定范围内的素数
- 判断一个数是否是素数
我们尝试进行重构,将这两个职责独立出来,放入不同的方法:
package com.qupeng.fp.design.principle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SingleResponsibility {
public static void main(String[] args) {
System.out.println(collectPrimesSR(100));
}
public static List<Long> collectPrimesSR(int upTo) {
List<Long> primes = new ArrayList<>();
for (long i = 1; i <= upTo; i++) {
if (isPrime(i)) {
primes.add(i);
}
}
return Collections.unmodifiableList(primes);
}
private static boolean isPrime(long aLong) {
for (long i = 2; i < aLong; i++) {
if (0 == aLong % i) {
return false;
}
}
return true;
}
}
第二个版本好了很多,我们可以替换不同的素数算法,素数算法也可以被重用。
再深入一点,我们能不能在进一步进行职责的拆分呢?Lambda表达式给了我们这种可能。我们发现,对数字的循环占据了很大的部分,我们把这部分交给类库,只聚焦于业务逻辑:
package com.qupeng.fp.design.principle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.LongStream;
public class SingleResponsibility {
public static void main(String[] args) {
System.out.println(collectPrimesSRLambda(100));
}
public static List<Long> collectPrimesSRLambda(int upTo) {
return LongStream.rangeClosed(1, upTo).filter(SingleResponsibility::isPrimeLambda).collect(ArrayList::new, (lst, aLong) -> lst.add(aLong), (lst1, lst2) -> lst1.addAll(lst2));
}
private static boolean isPrimeLambda(long aLong) {
return LongStream.range(2, aLong).noneMatch(num -> 0 == aLong % num);
}
}
3.4.1.2 开闭原则
软件应该对扩展开放,对修改闭合。
抽象和多态,为面向对象的开闭原则提供了支持。
高阶函数也提供了类似的特性,可以通过参数注入Lambda表达式,为类添加新的行为保留了扩展。
例如通过给ThreadLocal添加新的高阶函数工厂方法withInitial,程序员可以传入任意的Supplier,使得扩展ThreadLocal的初始化变得非常容易。
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
3.4.1.3 依赖反转原则
抽象不应该依赖细节,细节应该依赖抽象。
这个原则说具体一点就是面向接口的编程,上层业务逻辑依赖抽象的接口,而不依赖底层的实现细节。这样,当底层细节发生变化时,上层逻辑可以不必变更;反之,当上层业务发生变化时,底层逻辑也可以重用。
高阶函数可以提供反转控制。
来看个例子,从文件中查找key,并输出对应的value。
package com.qupeng.fp.design.principle;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class DependencyInversion {
public static void main(String[] args) {
System.out.println(findKeyInFile("c", "C:\\Users\\Administrator\\IdeaProjects\\java-demo\\resources\\dependency-inversion.txt"));
}
public static List<String> findKeyInFile(String key, String filePath) {
List<String> results = new ArrayList<>();
FileReader fileReader = null;
try {
fileReader = new FileReader(filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
do {
try {
line = bufferedReader.readLine();
if (null != line && line.contains(key)) {
results.add(line.substring(2));
}
} catch (IOException e) {
e.printStackTrace();
line = null;
}
}
while (line != null);
return results;
}
}
这段代码中,业务逻辑依赖于文件操作细节,如果将文件换成数据库,整个业务逻辑代码全部要重写。
下面我们利用高阶函数来重构业务逻辑代码:
package com.qupeng.fp.design.principle;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DependencyInversion {
public static void main(String[] args) {
System.out.println(findKeyInFileDI("d", () -> {
try {
return Optional.ofNullable(new FileReader("C:\\Users\\Administrator\\IdeaProjects\\java-demo\\resources\\dependency-inversion1.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return Optional.empty();
}, (key, lines) -> lines.filter(line -> line.contains(key)).map(line -> line.substring(2)).collect(Collectors.toList())));
}
public static List<String> findKeyInFileDI(String key, Supplier<Optional<Reader>> readerSupplier, BiFunction<String, Stream<String>, List<String>> handler) {
return readerSupplier.get().isPresent() ? handler.apply(key, new BufferedReader(readerSupplier.get().get()).lines()) : Arrays.asList();
}
}
在新版代码中,将业务接口重构为高阶函数,将数据处理逻辑与数据源抽象为函数接口,客户端用Lambda函数注入具体业务逻辑和底层数据读取,可以不修改而重用已有接口findKeyInFileDI。
3.4.2 Lambda表达式改变了设计模式
3.4.2.1 策略模式
策略模式支持在运行时改变软件的算法行为。
传统的策略模式示例:
package com.qupeng.fp.design.pattern;
public class StrategyPatternContext {
public static void main(String[] args) {
IStrategy stratergy = IStrategy.of("2");
StrategyPatternContext stratergyPatternContext = new StrategyPatternContext(stratergy);
System.out.println(stratergyPatternContext.calculate());
}
private IStrategy stratergy;
public StrategyPatternContext(IStrategy stratergy) {
this.stratergy = stratergy;
}
private String calculate() {
return this.stratergy.calculate("");
}
}
interface IStrategy {
String calculate(String data);
static IStrategy of(String type) {
switch (type) {
case "1":
return new ConcreteStrategy1();
case "2":
return new ConcreteStrategy2();
default:
return new DefaultStrategy();
}
}
}
class ConcreteStrategy1 implements IStrategy {
@Override
public String calculate(String data) {
return "Concrete strategy 1";
}
}
class ConcreteStrategy2 implements IStrategy {
@Override
public String calculate(String data) {
return "Concrete strategy 2";
}
}
class DefaultStrategy implements IStrategy {
@Override
public String calculate(String data) {
return "Default concrete strategy";
}
}
使用Lambda的策略模式示例,使用高阶函数为策略上下文直接注入Lambda表达式,消除样板代码和多余的类:
package com.qupeng.fp.design.pattern;
import java.util.function.Function;
public class StrategyPatternLambdaContext {
public static void main(String[] args) {
StrategyPatternLambdaContext strategyPatternContext = new StrategyPatternLambdaContext(str -> "Strategy 1");
System.out.println(strategyPatternContext.calculate());
}
private Function<String, String> strategy;
public StrategyPatternLambdaContext(Function<String, String> strategy) {
this.strategy = strategy;
}
private String calculate() {
return this.strategy.apply("");
}
}
3.4.2.2 命令模式
命令模式用一个命令对象,封装一个业务处理的所有细节。传统的命令模式,包含客户端,发起者,命令接口,具体命令,命令接受者,简单示例如下:
package com.qupeng.fp.design.pattern;
import java.util.ArrayList;
import java.util.List;
// 客户端
public class CommandPatternClient {
public static void main(String[] args) {
CommandPatternClient commandPattern = new CommandPatternClient();
commandPattern.runMacro();
}
public void runMacro() {
Macro macro = new Macro();
macro.record(new ConcreteCommand1());
macro.record(new ConcreteCommand2());
macro.run();
}
// 发起者
public class Macro {
private String prefix = "Macro: ";
private List<ICommand> commands = new ArrayList<ICommand>();
public void run() {
for (ICommand command : commands) {
prefix += command.run("");
}
System.out.println(prefix);
}
public void record(ICommand command) {
commands.add(command);
}
}
// 命令接口
public interface ICommand {
String run(String param);
}
// 具体命令实现
public class ConcreteCommand1 implements ICommand {
private CommandAcceptor commandAcceptor = new CommandAcceptor();
@Override
public String run(String param) {
return commandAcceptor.run("1");
}
}
public class ConcreteCommand2 implements ICommand {
private CommandAcceptor commandAcceptor = new CommandAcceptor();
@Override
public String run(String param) {
return commandAcceptor.run("2");
}
}
// 命令接收者
public class CommandAcceptor {
public String run(String index) {
return " run command-" + index;
}
}
}
使用函数接口替代命令接口,Lambda表达式替换命令实现,可以消除样板代码:
package com.qupeng.fp.design.pattern;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class CommandPatternLambdaClient {
public static void main(String[] args) {
CommandPatternLambdaClient commandPattern = new CommandPatternLambdaClient();
commandPattern.runMacro();
}
public void runMacro() {
Macro macro = new Macro();
CommandAcceptor commandAcceptor = new CommandAcceptor();
macro.record(str -> commandAcceptor.run("1"));
macro.record(str -> commandAcceptor.run("2"));
macro.run();
}
public class Macro {
private String prefix = "Macro: ";
private List<Function> commands = new ArrayList<>();
public void run() {
commands.forEach(command -> {
prefix += command.apply("");
});
System.out.println(prefix);
}
public void record(Function command) {
commands.add(command);
}
}
public class CommandAcceptor {
public String run(String index) {
return " run command-" + index;
}
}
}
3.4.2.3 观察者模式
在观察者模式中,被观察者持有一个观察者列表。当被观察者状态发生变化时,通知所有观察者。观察者模式典型应用就是前端UI,UI组件注册Listener来监听UI事件。
传统的观察者模式,一般会定义一个观察者和一个被观察者接口。
package com.qupeng.fp.design.pattern;
public class ObserverPattern {
interface Observer<T> {
void update(Observable o, T arg);
}
interface Observable<T> {
void addObserver(Observer observer);
void notifyObservers(T arg);
}
}
使用Lambda表达式实现的观察者模式,Lambda表达式取代了观察者的接口:
package com.qupeng.fp.design.pattern;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
public class ObserverPatternLambda {
public static void main(String[] args) {
MyObservable myObservable = new MyObservable();
myObservable.addObserver(((observable, s) -> System.out.println("Observer 1 get message from obserable: " + s)));
myObservable.addObserver(((observable, s) -> System.out.println("Observer 2 get message from obserable: " + s)));
myObservable.addObserver(((observable, s) -> System.out.println("Observer 3 get message from obserable: " + s)));
myObservable.notifyObservers("Hello observer!");
}
}
interface Observable<T> {
void addObserver(BiConsumer<Observable, T> observer);
void notifyObservers(T arg);
}
class MyObservable implements Observable<String> {
private List<BiConsumer<Observable, String>> observers = new ArrayList<>();
@Override
public void addObserver(BiConsumer<Observable, String> observer) {
observers.add(observer);
}
@Override
public void notifyObservers(String arg) {
observers.forEach(observer -> observer.accept(this, arg));
}
}
3.4.2.4 模板方法模式
模板方法模式用抽象类表示一个整体算法,一系列的抽象方法表示算法中可以被定制的步骤,还包含一些通用的代码。具体的类继承抽象算法类,复写抽象方法,从而实现不同的算法变种。
普通的模板方法模式示例:
package com.qupeng.fp.design.pattern;
public class TemplateMethod {
public static void main(String[] args) {
ConcreteProcessor concreteProcessor = new ConcreteProcessor();
System.out.println(concreteProcessor.process());
}
}
abstract class AProcessor {
public String process() {
String result = "Result is: ";
result = step1(result);
result = step2(result);
result = step3(result);
return result;
}
protected abstract String step3(String param);
protected abstract String step2(String param);
protected abstract String step1(String param);
}
class ConcreteProcessor extends AProcessor{
@Override
public String step1(String param) {
return param + "a";
}
@Override
public String step2(String param) {
return param + "b";
}
@Override
public String step3(String param) {
return param + "c";
}
}
用Lambda表达式实现的模板方法模式,用Lambda表达式替换抽象方法,算法类都可以不必是抽象的:
package com.qupeng.fp.design.pattern;
import java.util.function.Function;
public class TemplateMethodLambda {
public static void main(String[] args) {
Processor processor = new Processor(str -> str + "1", str -> str + "2", str -> str + "3");
System.out.println(processor.process());
ExtendedProcessor extendedProcessor = new ExtendedProcessor(new ExtendedProcessorSupport());
System.out.println(extendedProcessor.process());
}
}
class Processor {
private Function<String, String> step1;
private Function<String, String> step2;
private Function<String, String> step3;
public Processor(Function<String, String> step1, Function<String, String> step2, Function<String, String> step3) {
this.step1 = step1;
this.step2 = step2;
this.step3 = step3;
}
public String process() {
String result = "Result is: ";
result = step1.apply(result);
result = step2.apply(result);
result = step3.apply(result);
return result;
}
}
class ExtendedProcessor extends Processor{
public ExtendedProcessor(ExtendedProcessorSupport concreteProcessorSupport) {
super(concreteProcessorSupport::step1, concreteProcessorSupport::step2, concreteProcessorSupport::step3);
}
}
class ExtendedProcessorSupport {
public String step1(String param) {
return param + "a";
}
public String step2(String param) {
return param + "b";
}
public String step3(String param) {
return param + "c";
}
}
上面有两种形式,略有不同,第一种用高阶函数传入Lambda表达式,第二种使用方法引用。
3.4.2.5 构建者模式(Builder Pattern)
其核心思想是:如果有一类复杂对象,这些对象有相似的地方,包含很多部件(成员属性和方法),那么要构建一个或多个这样的对象的过程就会很复杂。如果能将构建这些对象的公共过程抽象出来,将“复杂对象的构建算法”与它的“部件和组装方式”分离,使得构建算法和组装方式可以独立应对变化;复用同样的构建算法可以创建不同复杂对象,不同的构建过程可以复用相同的部件组装方式。
来看一个通常的构件者模式的示例:
package com.qupeng.fp.design.pattern;
public class BuilderPattern {
public static void main(String[] args) {
new BuilderPattern().test();
}
void test() {
Director director = new Director();
Product product = director.construct();
System.out.println(product.toString());
}
// 有一类复杂产品,包含有三个复杂部件需要构建
class Product {
String part1;
String part2;
String part3;
@Override
public String toString() {
return part1 + part2 + part3;
}
}
// 抽象的构建算法
interface Builder {
void buildPart1();
void buildPart2();
void buildPart3();
Product get();
}
// 具体的部件的构造细节
class ConcreteBuilder implements Builder {
private Product product = new Product();
@Override
public void buildPart1() {
product.part1 = "Hello ";
}
@Override
public void buildPart2() {
product.part2 = "world ";
}
@Override
public void buildPart3() {
product.part3 = "!";
}
@Override
public Product get() {
return product;
}
}
class Director {
private Builder builder = new ConcreteBuilder();
// 抽象的构建流程
public Product construct() {
builder.buildPart3();
builder.buildPart1();
builder.buildPart2();
return builder.get();
}
}
}
使用高阶函数表示抽象算法,使用Lambda表达式替换具体的部件的构造,这样的好处是减少了具体构建者类的数量,Lambda表达式的重用也更加灵活。示例如下:
package com.qupeng.fp.design.pattern;
import java.util.function.Consumer;
import java.util.function.Function;
public class BuilderPatternLambda {
public static void main(String[] args) {
new BuilderPatternLambda().test();
}
void test() {
Pipeline pipeline = new Pipeline();
Automobile automobile = pipeline.construct();
System.out.println(automobile);
}
}
interface Automobile {
}
enum State {
STOP,
RUNNING;
}
class Engine {
double displacement;
State state = State.STOP;
@Override
public String toString() {
return "Engine{" +
"displacement=" + displacement +
", state=" + state +
'}';
}
}
class Shell {
String color;
@Override
public String toString() {
return "Shell{" +
"color='" + color + '\'' +
'}';
}
}
class Tire {
int number;
@Override
public String toString() {
return "Tire{" +
"number=" + number +
'}';
}
}
class Car implements Automobile {
String brand;
String name;
Engine engine;
Shell shell;
Tire tire;
Consumer<Car> startEngine;
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", name='" + name + '\'' +
", engine=" + engine +
", shell=" + shell +
", tire=" + tire +
'}';
}
}
interface Builder<B extends Builder<B, A>, A extends Automobile> {
B buildEngine(double displacement, Function<A, Engine> buildEngine);
B buildShell(String color, Function<A, Shell> buildShell);
B buildTire(int number, Function<A, Tire> buildTire);
B startEngine(Consumer<A> startEngine);
A get();
}
class CarBuilder implements Builder<CarBuilder, Car> {
private Car car = new Car();
private Function<Car, Engine> buildEngine;
private Function<Car, Shell> buildShell;
private Function<Car, Tire> buildTire;
private double engineDisplacement;
private String shellColor;
private int numberOfTire;
private AutomobileSpec automobileSpec;
public CarBuilder(AutomobileSpec carSpec) {
this.automobileSpec = carSpec;
}
@Override
public CarBuilder buildEngine(double displacement, Function<Car, Engine> buildEngine) {
this.engineDisplacement = displacement;
this.buildEngine = buildEngine;
return this;
}
@Override
public CarBuilder buildShell(String color, Function<Car, Shell> buildShell) {
this.shellColor = color;
this.buildShell = buildShell;
return this;
}
@Override
public CarBuilder buildTire(int number, Function<Car, Tire> buildTire) {
this.numberOfTire = number;
this.buildTire = buildTire;
return this;
}
@Override
public CarBuilder startEngine(Consumer<Car> startEngine) {
this.car.startEngine = startEngine;
return this;
}
@Override
public Car get() {
car.brand = this.automobileSpec.brand;
car.name = this.automobileSpec.name;
car.engine = this.buildEngine.apply(car);
car.engine.displacement = this.engineDisplacement;
car.tire = this.buildTire.apply(car);
car.tire.number = this.numberOfTire;
car.shell = buildShell.apply(car);
car.shell.color = this.shellColor;
car.startEngine.accept(car);
return car;
}
}
class Automobiles {
static Builder from(AutomobileSpec automobileSpec) {
switch (automobileSpec.category) {
case CAR:
default:
return new CarBuilder(automobileSpec);
}
}
}
class AutomobileSpec {
String brand = "";
String name = "";
Category category;
AutomobileSpec name(String name) {
this.name = name;
return this;
}
AutomobileSpec brand(String brand) {
this.brand = brand;
return this;
}
AutomobileSpec category(Category category) {
this.category = category;
return this;
}
}
enum Category {
CAR;
}
class Pipeline {
public Automobile construct() {
return ((CarBuilder)Automobiles.from(new AutomobileSpec().brand("DasAuto").name("Benz").category(Category.CAR)))
.buildEngine(2.0, car -> car.engine = new Engine())
.buildShell("black", car -> car.shell = new Shell())
.buildTire(20, car -> car.tire = new Tire())
.startEngine(car -> car.engine.state = State.RUNNING)
.get();
}
}