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

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

相关文章

高效+灵活,万博智云全球发布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 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

springboot实战学习(1)(开发模式与环境)

目录 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 (3)前端 二、开发模式 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 Validation:做参数校验Mybatis:做数据库的操作Redis:做缓存Junit:单元测试项目部署:springboot项目部署相关的知识 (3)前端 Vite:Vue项目的脚手架Router:路由Pina:状态管理Eleme

状态模式state

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/state 在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 在状态模式中,player.getState()获取的是player的当前状态,通常是一个实现了状态接口的对象。 onPlay()是状态模式中定义的一个方法,不同状态下(例如“正在播放”、“暂停

软件架构模式:5 分钟阅读

原文: https://orkhanscience.medium.com/software-architecture-patterns-5-mins-read-e9e3c8eb47d2 软件架构模式:5 分钟阅读 当有人潜入软件工程世界时,有一天他需要学习软件架构模式的基础知识。当我刚接触编码时,我不知道从哪里获得简要介绍现有架构模式的资源,这样它就不会太详细和混乱,而是非常抽象和易