基于lambda简化设计模式

news2024/11/16 8:20:11

前言

虽说使用设计模式可以让复杂的业务代码变得清晰且易于维护,但是某些情况下,开发可能会遇到我为了简单的业务逻辑去适配设计模式的情况,本文笔者就以四种常见的设计模式为例,演示如何基于lambda来简化设计模式的实现。

策略模式

我们的项目中会涉及各种各样的校验,可能是校验电话号码、单双数、字符串长度等,为此我们希望通过策略模式来封装这些校验规则。

第一步自然是通过接口来定义策略,编写一个名为execute方法,让用户传入字符串,返回校验结果的布尔值:

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

然后将这个策略接口聚合到我们的校验器中,后续我们就可以按照需求传入对应的校验策略即可:

/**
 * 校验工具,将策略接口成员成员属性,起到依赖抽象的作用
 */
public class Validator {
    private ValidationStrategy strategy;

    public Validator() {
    }

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

    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]+");
    }
}

同理,如果我们需要判断是否全为数字,则可以这样写:

/**
 * 判断传入字符是否全为数字
 */
public class IsNumeric implements ValidationStrategy {
    @Override
    public boolean execute(String str) {
        return str.matches("\\d+");
    }
}

使用时,我们只需按需传入校验规则即可:

public class Main {
    public static void main(String[] args) {
        //校验是否全为数字
        Validator v1 = new Validator(new IsNumeric());
        System.out.println(v1.validate("1234"));
        //校验是否全是小写
        Validator v2 = new Validator(new IsAllLowerCase());
        System.out.println(v2.validate("dalhl"));

    }
}

输出结果如下:

true
true

不知道读者是否可以发现问题,规则的校验往往只是一两行代码,为了适配规则校验所用到的策略模式,开发者往往需要对此额外创建一个类,要知道字符校验的规则是成百上千的,并且很多校验规则很可能仅仅是某个业务才会用到的。

所以我们是否有办法做到既能适配策略模式,又避免为了一段简单的校验代码而去创建一个类呢?

查看我们校验策略接口ValidationStrategy的定义,它要求传入一个String返回一个boolean,由此我们想到了java8提供的函数时接口Function,其定义如下所示,可以根据泛型要求要指明泛型TRapply方法要求传入一个T,这里可以直接理解为我们的String,然后返回一个R,同理代入我们的boolean:

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

	.......

}

按照java8lambda语法糖,Function<T, R>只有一个需要实现的方法R apply(T t),我们完全可以表面类的创建,取而代之的是这样一段表达式:

t->R

查看我们ValidationStrategy的定义,它也是只有一个方法execute,我们完全可以将其视为Function<String, Boolean>,即可得表达式s->boolean

在这里插入图片描述

由此我们得出下面这段代码,可以看到根据接口的定义匹配java8对应的函数时接口,然后基于lambda表达式即可完成创建,这样做法避免了类的生命,避免了简单逻辑复杂化实现的问题:

public static void main(String[] args) {
        //校验是否全为数字
        Validator v1 = new Validator((s) -> s.matches("\\d+"));
        System.out.println(v1.validate("1234"));
        //校验是否全是小写
        Validator v2 = new Validator(s -> s.matches("[a-z]+"));
        System.out.println(v2.validate("dalhl"));

    }

模板方法

银行接待VIP顾客的核心流程为:

  1. 查询顾客是否是VIP
  2. 招待顾客,为顾客办理业务。

所有银行的大体流程都是这样,唯一的区别就是第2步,对此我们可以使用模板方法模式,创建一个抽象类,将第1步抽出来,而第2步按照不同银行进行不同的实现:

public abstract class Banking {

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

    private String getCustomerWithId(int id) {
        return RandomUtil.randomString(5);
    }

    protected abstract void makeCustomerHappy(String customer);
}

对应两个银行的实现代码,先来看看BankingA 的招待逻辑:

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

BankingB的招待逻辑:

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

测试代码如下:

public static void main(String[] args) {
        BankingA bankingA = new BankingA();
        bankingA.processCustomer(1);

        BankingB bankingB= new BankingB();
        bankingB.processCustomer(1);

    }

对应输出结果:

6brkb吃饭,并为其办理业务
请autjm喝茶,并为其办理业务

还是一样的问题,找到会员是一段无返回值的简单输出,为了适配模板方法,这一行代码也还是要创建一个类,所以我们还是需要用lambda对其进行简化。

查看抽象方法makeCustomerHappy的定义,它要求传入一个传入而返回一个void,查阅java8对应的函数式接口,我们找到了Consumer

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

于是我们得出公式s->Void

在这里插入图片描述

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

public abstract class Banking {

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

    private String getCustomerWithId(int id) {
        return RandomUtil.randomString(5);
    }

}

这样一来,后续的调用即可用一段lambda实现:

public class Main {
    public static void main(String[] args) {
        Banking bankingA = new Banking();
        bankingA.processCustomer(1,customer-> System.out.println("请"+customer+"吃饭,并为其办理业务"));

        Banking bankingB = new Banking();
        bankingB.processCustomer(1,customer-> System.out.println("请"+customer+"喝茶,并为其办理业务"));

    }
}

观察者模式

观察者模式算是最经典也最好理解的设计模式,观察者只需将自己注册到感兴趣的主题上,一旦有主题更新就会及时通知观察者,观察者按照自己的需要进行响应处理。

对此我们首先定义观察者的接口:

/**
 * 观察者
 */
public interface Observer {
    void inform(String msg);
}

接下来就是主题:

public interface Subject {

    void registerObserver(Observer observer);

    void notifyObserver();
}

创建一个观察者1以及观察者2以及实现他们对自己感兴趣主题时会做出的反馈输出方法inform:

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

public class Observer2 implements Observer {
    @Override
    public void inform(String msg) {
        System.out.println("观察者2收到通知,内容为:" + 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.registerObserver(new Observer2());
        //主题发起通知
        subJect1.notifyObserver();
    }

输出结果:

观察者1收到通知,内容为:请大家学习《基于lambda简化设计模式》
观察者2收到通知,内容为:请大家学习《基于lambda简化设计模式》

很明显的Observer的inform是典型的Consumer接口,我们直接将其简化:

public static void main(String[] args) {
        SubJect1 subJect1 = new SubJect1("请大家学习《基于lambda简化设计模式》");
        //注册订阅者
        subJect1.registerObserver(s -> System.out.println("观察者1收到消息" + s));
        subJect1.registerObserver(s -> System.out.println("观察者2收到消息" + s));
        //主题发起通知
        subJect1.notifyObserver();
    }

责任链模式

我们希望字符串被对象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函数式接口实现表达式。

UnaryOperator源码可知,它继承Function,我们只需传入泛型T即可得到一个Function<T, T>,从而让我们得到一个T->TFunction表达式:

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

    /**
     * Returns a unary operator that always returns its input argument.
     *
     * @param <T> the type of the input and output of the operator
     * @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

而责任连的方式也很简单,因为UnaryOperator是Function的子类,这意味着我们可以使用FunctionandThen将所有的UnaryOperator完成衔接:

 UnaryOperator<String> p1 = i -> i.replace(",", ",");
        UnaryOperator<String> p2 = i -> i.replace("。", ".");

        p1.andThen(p2);
        System.out.println(p1.apply("hello,world。"));

小结

为了适配设计模式常会出现为了一段简单的逻辑,而去编写大量实现类的情况,所以我们建议,对于逻辑比较简单且需要适配设计模式的功能,可以尝试找到合适的函数式接口简化功能的实现,避免大量类文件的声明。

参考

Java 8 in Action

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1299407.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

postgresql自带指令命令系列二

简介 在安装postgresql数据库的时候会需要设置一个关于postgresql数据库的PATH变量 export PATH/home/postgres/pg/bin:$PATH&#xff0c;该变量会指向postgresql安装路径下的bin目录。这个安装目录和我们在进行编译的时候./configure --prefix [指定安装目录] 中的prefix参…

TypeScript中的单件设计模式

基本概念 &#xff08;1&#xff09; 了解设计模式 设计模式通俗的讲&#xff0c;就是一种更好的编写代码方案&#xff0c;打个比喻&#xff1a;从上海到武汉&#xff0c;你可以选择做飞机&#xff0c;做轮船&#xff0c;开车&#xff0c;骑摩托车多种方式&#xff0c;把出行…

【Kubernetes】四层代理Service

Service四层代理 一、Service概念原理1.1、为什么要有Service1.2、Service概述1.3、工作原理1.4、三类IP地址【1】Node Network&#xff08;节点网络&#xff09;【2】Pod network&#xff08;pod 网络&#xff09;【3】Cluster Network&#xff08;服务网络&#xff09; 二、S…

四川技能大赛——2023年四川网信人才技能大赛(网络安全管理员赛项)决赛

四川技能大赛——2023年四川网信人才技能大赛&#xff08;网络安全管理员赛项&#xff09;决赛 文章目录 四川技能大赛——2023年四川网信人才技能大赛&#xff08;网络安全管理员赛项&#xff09;决赛C1-比64少的bas - DONEC2-affine - DONEC3-简单的RSA - DONEM1-不要动我的f…

【C++数据结构 | 字符串速通】10分钟秒杀字符串相关操作 | 字符串的增删改查 | 字符串与数组相互转换

字符串 by.Qin3Yu 文中所有代码默认已使用std命名空间且已导入部分头文件&#xff1a; #include <iostream> #include <string> using namespace std;概念速览 字符串是一种非常好理解的数据类型&#xff0c;它用于存储和操作文本数据。字符串可以包含任意字符…

认识存储管理

存储器是计算机系统中最重要的资源之一。因为任何程序和数据以及各种控制用的数据结构都必须占有一定的存储空间&#xff0c;因此&#xff0c;存储管理直接影响系统性能。 存储器由内存和外存组成。内存是由系统实际提供的存储单元&#xff08;常指字节&#xff09;组成的一个连…

delphi android打开外部文件,报错android.os.FileUriExposedException解决方法

Android 7.0强制启用了被称作 StrictMode的策略&#xff0c;带来的影响就是你的App对外无法暴露file://类型的URI了。 如果你使用Intent携带这样的URI去打开外部App(比如&#xff1a;打开系统相机拍照)&#xff0c;那么会抛出FileUriExposedException异常。 Delphi 为Android…

spring集成mybatis简单教程

首先说下实现了什么效果&#xff0c;就是不用每次查询前手动创建 sessionFactory和添加datasource文件了。 整个工程结构是这样的 这次我也把代码放在了gitee上&#xff0c;方便大家更全貌的看到所有的实现细节。代码链接如下&#xff1a; Java: 一些Java代码 (gitee.com) …

交换机基本原理和配置

目录 一、数据链路层功能 二、交换机的工作原理 三、交换机的四大功能 一、数据链路层功能 位于网络层与物理层之间 数据链路的建立、维护与拆除帧包装、帧传输、帧同步帧的差错恢复流量控制 二、交换机的工作原理 交换机通过数据帧的源 MAC 地址&#xff0c;学习到交换机端…

生成模型 | 数字人类的三维重建(3D reconstruction)调研及总结【20231210更新版】

本文主要集中于图片到三维重建的算法模型&#xff0c;其中包含人体重建&#xff0c;人脸重建等 1.三维人体重建 1.1.2015_SMPL: A Skinned Multi-Person Linear Model 论文地址&#xff1a;SMPL2015.pdf (mpg.de) 代码地址&#xff1a;CalciferZh/SMPL: NumPy, TensorFlow an…

我的隐私计算学习——隐私集合求交(1)

笔记内容来自多本书籍、学术资料、白皮书及ChatGPT等工具&#xff0c;经由自己阅读后整理而成。 &#xff08;一&#xff09;PSI的介绍 隐私计算关键技术&#xff1a;隐私集合求交&#xff08;PSI&#xff09;原理介绍 隐私计算关键技术&#xff1a;隐私集合求交&#xff08…

利用Node.js和cpolar实现远程访问,无需公网IP和路由器设置的完美解决方案

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

Python开发运维:Python垃圾回收机制

目录 一、理论 1.Python垃圾回收机制 一、理论 1.Python垃圾回收机制 &#xff08;1&#xff09;引⽤计数器 1&#xff09;环状双向链表 refchain 在python程序中创建的任何对象都会放在refchain链表中。 name "david" age 20 hobby ["篮球",游泳…

【链表Linked List】力扣-114 二叉树展开为链表

目录 题目描述 解题过程 官方题解 题目描述 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应…

渗透测试——十、渗透列举及命令详解

渗透测试 一、协议配置与分析1、HTTPS 的定义2、HTTPS的验证 二、Kali Linux 常用工具三、Windows 命令详解 一、协议配置与分析 1、HTTPS 的定义 HTTPS (Hyper Text Transfer Protocol over Secure Socket Layer&#xff0c;超文本传输安全协议)是以安全为目标的 HTTP 通道。…

IDEA Maven 配置国内源

基本步骤 分别设置下图的两个&#xff0c;一个是对当前项目的设置&#xff0c;一个是对以后创建的项目设置&#xff0c;这样以后就不用重新配置了。 将下面的两个勾选上 注意&#xff0c;两个地方&#xff0c;Settings 和 Settings for New Projects 的勾都要勾上。 前往 User…

【C语言基础】嵌入式面试经典题(C语言篇)----有新的内容会及时补充、更新!

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

WorkPlus即时通讯app,支持私有化部署的企业IM

当企业面临复杂的协同、业务和生态场景时&#xff0c;多个繁琐的应用和系统常常让员工头疼不已。然而&#xff0c;WorkPlus作为企业数字化转型的超级APP&#xff0c;以其一站式全能解决方案&#xff0c;为企业带来了颠覆性的便利与高效。本文将深入探讨WorkPlus的特点和优势&am…

JavaScript系列-数据类型

ES6变量类型 JavaScript编程语言中&#xff0c;变量类型分为基本变量类型和引用类型&#xff0c;两种变量类型的区别在于 基本类型变量值存放于栈中&#xff0c;引用类型变量值存放于堆中基本类型赋值给其他变量&#xff0c;是将其值复制过去引用类型赋值给其他变量&#xff…

MyString:string类的模拟实现 1

MyString&#xff1a;string类的模拟实现 前言&#xff1a; 为了区分标准库中的string&#xff0c;避免编译冲突&#xff0c;使用命名空间 MyString。 namespace MyString {class string{private:char* _str;size_t _size;size_t _capacity;const static size_t npos -1;// C标…