设计模式之代理模式——闭包代理(初创篇)

2024-06-10 11:58

本文主要是介绍设计模式之代理模式——闭包代理(初创篇),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

场景还原

其实最开始, 我是在用 java swing 做一个 endpoint-io-transfer 的应用工具(一个用于从citrix下载文件的工具).

面板里面有几个按钮, 需要对按钮添加监听事件, 点击按钮执行逻辑功能, 相关代码大概是这样:

private JComponent getToolBar() {// ..... 无关代码省略 .....JButton singleScanButton = new JButton("单次扫描");singleScanButton.addActionListener(e -> {// ..... 省略按钮逻辑代码, 执行可能耗费500ms以上 .....});// ..... 无关代码省略 .....
}

但是有两个问题:

  1. java swing 的按钮执行是同步的, 在不加异步处理的情况下, 如果执行逻辑耗费时间较长会导致gui不响应.
  2. 短时间多次点击的话会导致按钮事件执行多次.

问题分析

解决上述问题方式比较简单

   采用异步线程, 加锁的方式就可以处理上面的问题.

但是按钮有10个左右, 如果用普通的写法, 会把代码搞得很难看, 那么就简化下代码呗!

   使用公共类或静态方法的形式将相关代码抽取出来, 就可以极大的简化代码

然而, 类似于这种 多次方法调用, 需要保证只有一次调用成功, 其它需要阻塞或返回 的功能需求太多了.

   如果仅仅是抽取公共方法, 或者是使用代理, 装饰模式之类的, 虽然能处理这个按钮事件的场景, 但是其它场景处理不了.

能不能有一种通用代码, 可以直接实现, 通过这个代码就可以直接处理掉类似的所有情况呢?

想到这里, 我盯上了方法中间的那个 lambda 表达式.

我想着 能不能通过一个方法对这个lambda表达式进行处理, 处理后的lambda表达式直接能够满足我的需求呢?

于是就捣鼓了下去…

OnceExecutorForConsumer

一会儿, 我就写好了这样的类

/**
* 将传入的 Consumer<T> then 作为 被代理函数式接口实例 传入, 将该对象的 onceExe 方法作为 代理方法实例 返回
* 
* 逻辑: 完成 函数式接口实例 的 异步单线程 代理
*/
public static class OnceExecutorForConsumer<T> {private final Lock lock = new ReentrantLock();/*** 被代理的方法*/private final Consumer<T> then;private Consumer<T> skip;public OnceExecutorForConsumer(Consumer<T> then) {this.then = then;}/*** 代理方法* * <p>*     每次调用新建一个线程对参数进行处理, 主线程不阻塞, 分线程竞争锁, 抢到运行 then, 抢不到运行 skip* </p>*/public void onceExe(final T e) {new Thread(() -> {if (lock.tryLock()) {try {if (then != null) {then.accept(e);}} finally {lock.unlock();}} else {if (skip != null) {skip.accept(e);}}}).start();}public OnceExecutorForConsumer<T> setSkip(Consumer<T> skip) {this.skip = skip;return this;}
}

原来的按钮部分代码就成了下面的调用方式

   private JComponent getToolBar() {// ..... 无关代码省略 .....JButton singleScanButton = new JButton("单次扫描");singleScanButton.addActionListener(new OnceExecutorForConsumer<>((ActionEvent e) -> {// ..... 省略按钮逻辑代码, 执行可能耗费500ms以上 .....}).setSkip(e -> log.debug("多次点击无效"))::onceExe);// ..... 无关代码省略 .....}

如此完美完成了既定目标

3. 分析 OnceExecutorForConsumer

实际上, 在写 OnceExecutorForConsumer 的时候, 我就已经发现了, OnceExecutorForConsumer 会是一个很强的代理类.

和静态代理不同, 这种代理方式代理的是函数式接口, 将一个函数式接口作为被代理对象传入, 传出一个代理的函数式接口, 然后这个函数式接口就可以实现对原有函数式接口对象的代理.

  1. 众所周知, 静态代理的弊端之一是代理类和被代理类需要实现同一个接口, 代理类, 被代理类 和接口之间耦合度很高, 同一个功能, 接口和被代理类发生变化, 那么代理类也要跟着变化, 代理类简直成适配器了.

    然而, 如果对函数式接口对象进行代理的话会怎么样呢?

    1. 相同结构的函数式接口可以相互转化.
    2. 函数式接口结构很少, 也就只有 参数和返回值的区别而已.
    3. 不通的函数式接口实例也可以通过简单的方式适配连接在一起.

    可见, 函数式接口实例的代理不会有 代理类和代理对象的接口问题, 导致代理类泛滥的问题, 这就使得这种代理模式相当灵活.

  2. 在 java 中, 万物皆对象, 对象中的方法也看作是一个对象, 这个对象可以转换为函数式接口实例.

    也就是说, 这种代理功能极其强大, 它能够直接和间接代理所有方法. 一类在手, 简直天下我有啊有木有.

  3. 这种代理方式主打成为针对方法的工具类, 相对于普通的方法操控方法来说, 它有一个闭包的优势, 闭包背后有一个完整的类, 这可以赋予他强大的功能.

起名:闭包代理

我们看下这种代理方式, 它是构造类的过程中将函数方法变成它的一个成员变量, 之后返回创建的类的另一个方法作为代理方法, 之后整个对象仅仅往外暴漏了一个方法, 也就是说因为这个方法被外界引用, 导致这个对象能够存活下去.

大家想到了什么, 这就是Js中的闭包逻辑啊!

虽然java中也有闭包, 但那个闭包我直接忽略了!

那么到这里, 实际上称呼也就定了, 闭包加代理, 那就闭包代理啊!

其实本来我想起名为函数代理函数式接口代理来着, 但是吧, 前一个感觉像数学, 后一个名字太长.

从此, java里面除了静态代理 & 动态代理之外, 至少在我的字典里面就可以新增一种代理方式了: 闭包代理


抽象集成

既然上面分析闭包代理很强大, 那我们来用一个例子说明.

我们通过引入下面一个问题

问题: 如下面的一个onClick方法, 它是一个很简单的处理逻辑, 但是应该如何改动它, 使之满足以下需求

  • 同一时间只应该被一个线程调用执行.
  • 方法同一时间被多次调用时, 不能被阻塞.
  • 若已经有线程正在调用它, 后续调用这个方法的线程应该退出或者处理其他逻辑.
   private void onClick(String msg) {log.info("start {}", msg);// - 业务逻辑 用 睡眠10 ms 表示 -Throws.con(10, Thread::sleep);log.info(" end  {}", msg);}

那么该怎么改动才能实现上面的需求呢?

答案很简单, 用上面写的 OnceExecutorForConsumer 类处理一下就行了, 但是上面那个还涉及到了异步处理, 异步处理平时用不到.

能不能对上面的类改造一下, 作为一个通用方法.

可以很容易地看出, onClick(String) 方法可以转化成一个函数接口 Consumer<String>.

能不能实现一个方法, 只要将 onClick(String) 作为参数传入, 就可以从返回值返回一个 Consumer<String> 方法. 返回的 Consumer<String> 方法就可以直接实现 上面三点需求.


OnceExecClosureProxy源码

具体实现逻辑不难, 只要把上面的 new Thread() 去掉就好了, 另外再加个静态方法, 就可以实现

```java
public static <T> T of(T then) {return new OnceExecClosureProxy<>(then).proxy();
}
```

现在直接给出 git 上面的 OnceExecClosureProxy代码, 这里面写的更加具体一些.

  1. git

    相关源码在 github 和 gitee 上, 上面有最新的代码.

    • github: https://github.com/cosycode/common-lang
    • gitee: https://gitee.com/cosycode/common-lang
  2. repo

    同时我也将代码打包成 jar, 发布到 maven 仓库

    Apache Maven

    <dependency><groupId>com.github.cosycode</groupId><artifactId>common-lang</artifactId><version>1.4</version>
    </dependency>
    

    gradle

    implementation 'com.github.cosycode:common-lang:1.4'
    

该类完整类路径是 com.github.cosycode.common.ext.proxy.OnceExecClosureProxy

除了这个类, 还有顺便写的其它的几个闭包代理类, 都在com.github.cosycode.common.ext.proxy包下.

想看自己去看吧, 有时间再去讲解.

使用 com.github.cosycode.common.ext.proxy.OnceExecClosureProxy 处理调用代码如下

@Test
public void onClickTest2() {Consumer<String> consumer = OnceExecutorForConsumer.of(this::onClick);// 并发调用测试IntStream.range(0, 5).parallel().forEach(num -> {consumer.accept(" time: " + num);log.info("--------------- 线程 {} 调用完成", num);});
}

执行日志如下, 从日志里面看到onClick调用逻辑之发生了一次.

[INFO] 16:05:51.122 | --------------- 线程 8 调用完成
[INFO] 16:05:51.122 | --------------- 线程 4 调用完成
[INFO] 16:05:51.122 | --------------- 线程 1 调用完成
[INFO] 16:05:51.122 | --------------- 线程 9 调用完成
[INFO] 16:05:51.122 | --------------- 线程 7 调用完成
[INFO] 16:05:51.122 | --------------- 线程 0 调用完成
[INFO] 16:05:51.122 | --------------- 线程 5 调用完成
[INFO] 16:05:51.122 | start  time: 6
[INFO] 16:05:51.122 | --------------- 线程 2 调用完成
[INFO] 16:05:51.122 | --------------- 线程 3 调用完成
[INFO] 16:05:51.150 |  end   time: 6
[INFO] 16:05:51.150 | --------------- 线程 6 调用完成

这篇文章关于闭包代理仅仅写了个开头,
如果你对后续内容感兴趣,
请看下一篇 JAVA 设计模式之代理模式——闭包代理(集成篇)

若是有什么错误或者是有什么使用建议, 欢迎大家留言

这篇关于设计模式之代理模式——闭包代理(初创篇)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1048103

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

大数据spark3.5安装部署之local模式详解

《大数据spark3.5安装部署之local模式详解》本文介绍了如何在本地模式下安装和配置Spark,并展示了如何使用SparkShell进行基本的数据处理操作,同时,还介绍了如何通过Spark-su... 目录下载上传解压配置jdk解压配置环境变量启动查看交互操作命令行提交应用spark,一个数据处理框架

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素