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

场景还原

其实最开始, 我是在用 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 设计模式之代理模式——闭包代理(集成篇)

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逆光影者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值