基于lambda简化设计模式

发布于:2024-08-15 ⋅ 阅读:(71) ⋅ 点赞:(0)

写在文章开头

本文将演示基于函数式编程的理念,优化设计模式中繁琐的模板化编码开发,以保证用尽可能少的代码做尽可能多的事,希望对你有帮助。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

详解lambda重构设计模式思路

策略模式

不同的业务对于字符串有着不同的匹配规则,例如:长度是否大于10、是否全是大小、是否是电话号码等,为了保证规则的复用,我们通常会采用策略模式封装这些规则:

在这里插入图片描述

对应的我们给出策略封装的接口定义:

/**
 * 定义策略模式的接口
 */
public interface ValidationStrategy {
    /**
     * 校验该字符串是否符合要求,若符合则返回true
     * @param str
     * @return
     */
    boolean execute(String str);
}

后续让我们的规则校验器将这个抽象的校验规则接口聚合进来,如此我们就可以按照需求传入对应的校验规则策略strategy

public class Validator {
    //将校验规则聚合进来
    private ValidationStrategy strategy;

    public Validator() {
    }

    public Validator(ValidationStrategy strategy) {
        this.strategy = strategy;
    }

    //基于聚合的strategy校验字符串str
    public boolean validate(String str) {
        return strategy.execute(str);
    }
}

以下便是判断字符串是否全小写的校验规则实现:

/**
 * 判断是否全为小写
 */
public class IsAllLowerCase implements ValidationStrategy {
    @Override
    public boolean execute(String str) {
        return str.matches("[a-z]+");
    }
}

完成后我们就可以将IsAllLowerCase实例化并传入校验器Validator 中进行使用了:

//校验是否全是小写
Validator validator = new Validator(new IsAllLowerCase());
 System.out.println(validator.validate("abc"));//true

可以看到我们的校验规则基本上一行就可以搞定,为了这仅有的一行逻辑实现而编写大量的前置模板实在繁琐,对此我们不妨基于之前所介绍的函数式编程的思路分析以下,我们的ValidationStrategy本质上就只有一个抽象接口,所以它完全可以被认定为一个函数式接口,所以我们不妨将FunctionalInterface这个注解加上:

@FunctionalInterface
public interface ValidationStrategy {
    /**
     * 校验该字符串是否符合要求,若符合则返回true
     * @param str
     * @return
     */
    boolean execute(String str);
}

参考其抽象方法,我们可以看到入参为String,返回值为boolean,由此推出函数描述符为(String)->boolean,我们以上文全小写规则为例,按照这个函数描述符的规则,我们的表达式就是:(s)->s.matches("[a-z]+"),是不是简化了许多呢?

在这里插入图片描述

对应的我们给出简化后的代码:

 //校验是否全是小写
        Validator validator = new Validator((s)->s.matches("[a-z]+"));
        System.out.println(validator.validate("abc"));

模板方法

模板方法的理念是对于固化模板自实现,然后将个性化业务下沉到不同业务场景自行开发,我们以银行找到会员为例,其大体步骤为:

  1. 查询会员信息(固定)。
  2. 招待会员。(不同银行流程不同)。

在这里插入图片描述

基于这个需求我们得出这样一个抽象类:

public abstract class Banking {

    public void processCustomer(int id) {
        //查询会员信息
        String customer = getCustomerWithId(id);
        //招待会员
        makeCustomerHappy(customer);
    }

    private String getCustomerWithId(int id) {
        return "user-"+id;
    }
    //不同银行有不同的实现
    protected abstract void makeCustomerHappy(String customer);
}

对应的我们给出BankingA 的招待逻辑:

public class BankingA extends Banking {
    @Override
    protected void makeCustomerHappy(String customer) {
        System.out.println("请"+customer+"吃饭,并为其办理业务");
    }
}

对应我们给出使用代码和输出结果:

Banking bankingA = new BankingA();
 bankingA.processCustomer(1);//输出请user-1吃饭,并为其办理业务

还是一样的问题,为了仅仅一段简单的输出去适配模板方法从而编写出一段繁琐的代码,很明显是需求简化的。

分析抽象方法makeCustomerHappy的定义,要求传入String返回void,由此我们按照函数式编程的套路得出可能要用到的函数式接口的函数描述符为:(String)->Void,于是我们得到了一个JDK8自带的接口Consumer,它的函数签名为(T)->Void,注意这个T是泛型,所以我们完全可以将String类等价代入:

在这里插入图片描述

这里我们也给出Consumer的源码,读者可自行查阅:

@FunctionalInterface
public interface Consumer<T> {
	//......
    void accept(T t);

}

对此我们将抽象类Banking 加以改造,将抽象方法makeCustomerHappy改为Consumer接口:

public class Banking {

    public void processCustomer(int id, Consumer<String> makeCustomerHappy) {
        //查询会员信息
        String customer = getCustomerWithId(id);
        //招待会员
        makeCustomerHappy.accept(customer);
    }

    private String getCustomerWithId(int id) {
        return "user-"+id;
    }
   
}

于是我们的代码就简化成了下面这样:

 Banking bankingA = new BankingA();
 bankingA.processCustomer(1, (s) -> System.out.println("请" + s + "吃饭,并为其办理业务"));

观察者模式

观察者模式算是最经典也最好理解的设计模式,观察者只需将自己注册到感兴趣的主题上,一旦有主题更新就会及时通知观察者处理该消息:

在这里插入图片描述

基于这种设计模式的理念,我们给出观察者的接口的定义:

/**
 * 观察者
 */
public interface Observer {
    //主题有更新调用该方法通知观察者
    void inform(String msg);
}

然后就是主题接口的定义:

public interface Subject {

    //注册观察者
    void registerObserver(Observer observer);
    
    //通知消息给所有观察者
    void notifyObserver();
}

对此我们给出一个观察者的实现:

public class Observer1 implements Observer {
    @Override
    public void inform(String msg) {
        System.out.println("观察者1收到通知,内容为:" + msg);
    }
}

最后就是主题类的实现,我们将观察者聚合,如果观察者对SubJect1 感兴趣,则通过registerObserver完成注册,一旦主题要发布新消息就可以通过notifyObserver及时通知每一个订阅者:

public class SubJect1 implements Subject {

    private String msg;


    public SubJect1(String msg) {
        this.msg = msg;
    }

    private List<Observer> observerList = new ArrayList<>();

    //将观察者添加到订阅者列表
    @Override
    public void registerObserver(Observer observer) {
        observerList.add(observer);
    }
    
    //有新消息通知订阅列表的所有用户
    @Override
    public void notifyObserver() {
        observerList.forEach(o -> o.inform(msg));
    }

测试代码和对应输出结果如下所示:

public static void main(String[] args) {
        SubJect1 subJect1 = new SubJect1("请大家学习《基于lambda简化设计模式》");
        //注册订阅者
        subJect1.registerObserver(new Observer1());
        //主题发起通知
        subJect1.notifyObserver();//观察者1收到通知,内容为:请大家学习《基于lambda简化设计模式》
    }

每一个观察都是一行代码,为了这一行创建一个类显得有些繁琐,仔细查看registerObserver方法的定义我们可以得出该方法的签名为Observer->Void,由此我们可以推导出对应的lambda表达式:

在这里插入图片描述

由此我们可以直接将上述Observer1的代码简化为lambda表达式,对应代码示例如下:

 subJect1.registerObserver(s -> System.out.println("观察者1收到消息" + s));

责任链模式

责任链在Spring或者Netty这种大型框架中非常常见,它通过链式关系顺序的调用处理器处理当前业务数据,举个例子,我们希望字符串被对象1处理完成之后要转交给对象2处理,并且我们后续可能还会交给更多的对象处理,通过对需求的梳理和抽象,这个功能可以通过责任链模式来实现。

在这里插入图片描述

首先声明责任链上每一个处理器的抽象类,考虑到通用性笔者将这个类的入参设置为泛型,并且公共方法handle的步骤为:

  1. 调用自己的handWork处理输入数据,handWork交给实现类自行编写。
  2. successor不为空,则将处理结果交给下一个处理器处理,由此构成一条处理链。
public abstract class ProcessingObject<T> {

    /**
     * 下一个处理器
     */
    private ProcessingObject<T> successor;

    public ProcessingObject<T> getSuccessor() {
        return successor;
    }

    public void setSuccessor(ProcessingObject<T> successor) {
        this.successor = successor;
    }

    public T handle(T input) {
        //先自己处理完,如果有后继责任链,则交给后面的责任链处理,递归下去
        T t = handWork(input);
        if (successor != null) {
            return successor.handWork(t);
        }
        return t;
    }

    /**
     * 自己的处理逻辑
     *
     * @param intput
     * @return
     */
    abstract T handWork(T intput);
}

对应的我们基于这个抽象类实现两个字符处理器,ProcessingStr1会将收到的中文逗号换位英文逗号:

public class ProcessingStr1 extends ProcessingObject<String> {


    @Override
    String handWork(String intput) {
        return intput.replace(",", ",");
    }
}

ProcessingStr2 会将中文句号替换为英文句号:

public class ProcessingStr2 extends ProcessingObject<String> {


    @Override
    String handWork(String intput) {
        return intput.replace("。", ".");
    }
}

测试代码和输出结果如下:

public static void main(String[] args) {
        ProcessingObject<String> p1 = new ProcessingStr1();
        ProcessingObject<String> p2 = new ProcessingStr2();
        p1.setSuccessor(p2);
        System.out.println(p1.handle("hello,world。"));
    }

可以看到所有的中文符号都被替换成英文符号了:

hello,world.

话不多说,不难看出上文这种传入String返回String的方法,我们完全可以使用UnaryOperator函数式接口,它的函数式签名为(T)->T,很明显将我们的String类代入是成立的:

在这里插入图片描述

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

   
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

于是我们的责任链的处理器就被简化为下面这样:

//逗号替换
 UnaryOperator<String> p1 = i -> i.replace(",", ",");
 //句号替换
UnaryOperator<String> p2 = i -> i.replace("。", ".");

为了让两个处理器构成逻辑链,我们直接使用函数复合方法andThen,确保p1执行玩调用p2:

Function<String, String> function = p1.andThen(p2);
System.out.println(function.apply("hello,world。"));//hello,world.

小结

对于逻辑比较简单且需要适配设计模式的功能,可以尝试找到合适的函数式接口,按照它的签名实现功能,避免没必要的模板配置工作。

以上便是笔者关于Java8中函数式设计模式的案例实践全部内容,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

在这里插入图片描述

参考

Java 8 in Action:https://book.douban.com/subject/25912747/