Java函数式编程(五)设计和架构的原则

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);
    }
}

程序运行没有任何问题,但是它违反了单一职责原则。这段代码有两个关键的职责:

  1. 收集指定范围内的素数
  2. 判断一个数是否是素数
    我们尝试进行重构,将这两个职责独立出来,放入不同的方法:
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();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值