1. 場景還原
其實最開始, 我是在用 java swing 做一個 endpoint-io-transfer 的應(yīng)用工具(一個用于從citrix下載文件的工具).
頁面里面有幾個按鈕, 點擊按鈕執(zhí)行邏輯功能, 相關(guān)代碼大概是這樣:
private JComponent getToolBar() {
// ..... 無關(guān)代碼省略 .....
JButton singleScanButton = new JButton("單次掃描");
singleScanButton.addActionListener(e -> {
// ..... 省略按鈕邏輯代碼, 執(zhí)行可能耗費500ms以上 .....
});
// ..... 無關(guān)代碼省略 .....
}
java swing 的按鈕執(zhí)行是同步的, 如果執(zhí)行邏輯耗費時間較長會導(dǎo)致gui不響應(yīng), 而且如果短時間多次點擊的話會導(dǎo)致執(zhí)行多次, 于是就想著處理一下.
處理方式很簡單, 就是用鎖和多線程搞一下就行了, 但是會把代碼搞得很難看.
按鈕有10個左右, 雖然可以使用公共類或靜態(tài)方法的形式簡化, 但是這次我卻盯上了中間的那個 lambda 表達(dá)式.
我想著 能不能對這個lambda表達(dá)式進(jìn)行處理, 處理后的lambda表達(dá)式直接能夠滿足我的需求呢?
于是就搗鼓了下去.
2. 處理點擊事件
一會兒, 我就寫好了這樣的類
/**
* 將傳入的 Consumer<T> then 作為 被代理函數(shù)式接口實例 傳入, 將該對象的 onceExe 方法作為 代理方法實例 返回
*
* 邏輯: 完成 函數(shù)式接口實例 的 異步單線程 代理
*/
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>
* 每次調(diào)用新建一個線程對參數(shù)進(jìn)行處理, 主線程不阻塞, 分線程競爭鎖, 搶到運行 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;
}
}
原來的按鈕部分代碼就成了下面的調(diào)用方式
private JComponent getToolBar() {
// ..... 無關(guān)代碼省略 .....
JButton singleScanButton = new JButton("單次掃描");
singleScanButton.addActionListener(new OnceExecutorForConsumer<>((ActionEvent e) -> {
// ..... 省略按鈕邏輯代碼, 執(zhí)行可能耗費500ms以上 .....
}).setSkip(e -> log.debug("多次點擊無效"))::onceExe);
// ..... 無關(guān)代碼省略 .....
}
如此完美完成了既定目標(biāo)
3. 分析 OnceExecutorForConsumer
實際上, 在寫 OnceExecutorForConsumer 的時候, 我就已經(jīng)發(fā)現(xiàn)了, OnceExecutorForConsumer 會是一個很強的代理類.
和靜態(tài)代理不同, 這種代理方式代理的是函數(shù)式接口, 將一個函數(shù)式接口作為被代理對象傳入, 傳出一個代理的函數(shù)式接口, 然后這個函數(shù)式接口就可以實現(xiàn)對原有函數(shù)式接口對象的代理.
-
眾所周知, 靜態(tài)代理的弊端之一是代理類和被代理類需要實現(xiàn)同一個接口, 代理類, 被代理類 和接口之間耦合度很高, 同一個功能, 接口和被代理類發(fā)生變化, 那么代理類也要跟著變化, 代理類簡直成適配器了.
然而, 如果對函數(shù)式接口對象進(jìn)行代理的話會怎么樣呢?
- 相同結(jié)構(gòu)的函數(shù)式接口可以相互轉(zhuǎn)化.
- 函數(shù)式接口結(jié)構(gòu)很少, 也就只有 參數(shù)和返回值的區(qū)別而已.
- 不通的函數(shù)式接口實例也可以通過簡單的方式適配連接在一起.
可見, 函數(shù)式接口實例的代理不會有
代理類和代理對象的接口問題, 導(dǎo)致代理類泛濫的問題, 這就使得這種代理模式相當(dāng)靈活. -
在 java 中, 萬物皆對象, 對象中的方法也看作是一個對象, 這個對象可以轉(zhuǎn)換為函數(shù)式接口實例.
也就是說, 這種代理功能極其強大, 它能夠直接和間接代理所有方法. 一類在手, 簡直天下我有啊有木有.
這種代理方式主打成為針對方法的工具類, 相對于普通的
方法操控方法來說, 它有一個閉包的優(yōu)勢, 閉包背后有一個完整的類, 這可以賦予他強大的功能.
起名:閉包代理
我們看下這種代理方式, 它是構(gòu)造類的過程中將函數(shù)方法變成它的一個成員變量, 之后返回創(chuàng)建的類的另一個方法作為代理方法, 之后整個對象僅僅往外暴漏了一個方法, 也就是說因為這個方法被外界引用, 導(dǎo)致這個對象能夠存活下去.
大家想到了什么, 這就是Js中的閉包邏輯啊!
雖然java中也有閉包, 但那個閉包我直接忽略了!
那么到這里, 實際上稱呼也就定了, 閉包加代理, 那就閉包代理啊!
其實本來我想起名為函數(shù)代理或函數(shù)式接口代理來著, 但是吧, 前一個感覺像數(shù)學(xué), 后一個名字太長.
從此, java里面除了靜態(tài)代理 & 動態(tài)代理之外, 至少在我的字典里面就可以新增一種代理方式了: 閉包代理
這篇文章關(guān)于閉包代理僅僅寫了個開頭,
如果你對后續(xù)內(nèi)容感興趣,
請看下一篇 JAVA 設(shè)計模式之代理模式——閉包代理(集成篇)