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

557 阅读4分钟

1. 场景还原

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

页面里面有几个按钮, 点击按钮执行逻辑功能, 相关代码大概是这样:

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

java swing 的按钮执行是同步的, 如果执行逻辑耗费时间较长会导致gui不响应, 而且如果短时间多次点击的话会导致执行多次, 于是就想着处理一下.

处理方式很简单, 就是用锁和多线程搞一下就行了, 但是会把代码搞得很难看.

按钮有10个左右, 虽然可以使用公共类或静态方法的形式简化, 但是这次我却盯上了中间的那个 lambda 表达式.

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

于是就捣鼓了下去.

2. 处理点击事件

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

/**
* 将传入的 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里面除了静态代理 & 动态代理之外, 至少在我的字典里面就可以新增一种代理方式了: 闭包代理


这篇文章关于闭包代理仅仅写了个开头,

如果你对后续内容感兴趣,

请看下一篇 JAVA 设计模式之代理模式——闭包代理(集成篇)