簡述
對客戶端隱藏目標類,創建代理類拓展目標類,并且對于客戶端隱藏功能拓展的細節,使得客戶端可以像使用目標類一樣使用代理類,面向代理(客戶端只與代理類互動),
話不多說,看一個優化案例,
優化案例
最初版v0
目前的功能是下載可以下載檔案,
public class BiliBiliDownloader {
public byte[] download(String filePath) throws InterruptedException {
System.out.printf("正在下載BiliBili檔案:%s%n", filePath);
// 模擬檔案下載,睡個10秒
Thread.sleep(10000);
return new byte[1024]; // 假裝是下載檔案的位元組陣列
}
}
客戶端呼叫代碼,如下,
public class Client {
public static void main(String[] args) throws InterruptedException {
BiliBiliDownloader bilidownloader = new BiliBiliDownloader();
bilidownloader.download("/root/buzuweiqi/java_manual.txt");
}
}
下載工具類對客戶端完全暴露,客戶端可以直接使用下載類實作下載,這實際上是無可厚非的,
經過研究發現,這個下載類有一個問題:每次呼叫都肯定會下載新的檔案,即便檔案已經被下載過,
為了解決這個問題,開發團隊經過商討已經有了一個初步的方案,看一下代碼樣例,
修改版v1
團隊決定使用傳統的修改方式(直接修改BiliBiliDownloader),認為這樣最為的直觀,確實,代碼量少且未來可以預期修改不頻繁時,傳統的修改方案也未嘗不是一個好的選擇,
public class BiliBiliDownloader {
// 定義用來快取資料的map物件
private static Map<String, byte[]> map = new HashMap<>();
public byte[] download(String filePath) throws InterruptedException {
System.out.printf("正在下載BiliBili檔案:%s%n", filePath);
if (map.containsKey(filePath)) {
return map.get(filePath);
}
// 模擬檔案下載,睡個10秒
Thread.sleep(10000);
byte[] res = new byte[1024]; // 假裝這是下載后的位元組陣列
map.put(filePath, res); // 加入快取
return res;
}
}
客戶端呼叫代碼,還是和原來一樣,
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
BiliBiliDownloader downloader = new BiliBiliDownloader();
downloader.download("/root/home/buzuweiqi/java_manual.txt");
// 由于檔案已經快取,所以這次下載非常快
downloader.download("/root/home/buzuweiqi/java_manual.txt");
// 由于檔案還未快取,所以這次下載比較緩慢
downloader.download("/root/home/buzuweiqi/linux_manual.txt");
}
}
到目前為止好像都沒有啥不妥的地方,直到有一天,客戶提出了新的需求:雖然現在只可以下載bilibili的檔案(視頻,音頻,文章等),以后還想要下載youtube的檔案,
為了實作這個需求,以及方便以后同類的需求變更,是時候用上代理模式,
修改版v2
代理模式在使用的時候需要頂一個一個頂層介面,并且使得代理類和被代理類都實作這個介面,
代理類中需要持有非代理類的一個物件,并且在呼叫代理類的功能前后可以根據業務需要拓展新的功能,
public interface Downloader {
byte[] download(String filePath) throws InterruptedException;
}
public class BiliBiliDownloader implements Downloader {
public byte[] download(String filePath) throws InterruptedException {
System.out.printf("正在下載BiliBili檔案:%s%n", filePath);
// 模擬檔案下載,睡個10秒
Thread.sleep(10000);
return new byte[1024]; // 假裝是下載檔案的位元組陣列
}
}
public class ProxyBiliBiliDownloader implements Downloader {
private static Map<String, byte[]> map = new HashMap<>();
private BiliBiliDownloader downloader = new BiliBiliDownloader();
public byte[] download(String filePath) throws InterruptedException {
if (map.containsKey(filePath)) {
System.out.printf("正在下載BiliBili檔案:%s%n", filePath);
return map.get(filePath);
}
byte[] res = downloader.download(filePath);
map.put(filePath, res);
return res;
}
}
public class YoutubeDownloader implements Downloader {
public byte[] download(String filePath) throws InterruptedException {
System.out.printf("正在下載Youtube檔案:%s%n", filePath);
// 模擬檔案下載,睡個10秒
Thread.sleep(10000);
return new byte[1024]; // 假裝是下載檔案的位元組陣列
}
}
public class ProxyYoutubeDownloader implements Downloader {
private static Map<String, byte[]> map = new HashMap<>();
private BiliBiliDownloader downloader = new BiliBiliDownloader();
public byte[] download(String filePath) throws InterruptedException {
if (map.containsKey(filePath)) {
System.out.printf("正在下載Youtube檔案:%s%n", filePath);
return map.get(filePath);
}
byte[] res = downloader.download(filePath);
map.put(filePath, res);
return res;
}
}
客戶端的使用案例如下,
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
Downloader downloader = new ProxyBiliBiliDownloader();
downloader.download("/root/home/buzuweiqi/java_manual.txt");
downloader = new ProxyYoutubeDownloader();
downloader.download("/root/home/buzuweiqi/linux_manual.txt");
}
}
客戶端不再依賴目標類,而是轉而依賴代理類,
代理模式使得增加相似需求時可以只增加一對實作類(目標類,代理類),而不用修改原本的類,符合開閉原則,
實際上通常我們會使用一個更為簡單的方式控制代理物件的創建:反射,
修改版v3
高層介面,實作的目標類、代理類依舊不變,
public interface Downloader {
byte[] download(String filePath) throws InterruptedException;
}
public class BiliBiliDownloader implements Downloader {
public byte[] download(String filePath) throws InterruptedException {
System.out.printf("正在下載BiliBili檔案:%s%n", filePath);
// 模擬檔案下載,睡個10秒
Thread.sleep(10000);
return new byte[1024]; // 假裝是下載檔案的位元組陣列
}
}
public class ProxyBiliBiliDownloader implements Downloader {
private static Map<String, byte[]> map = new HashMap<>();
private BiliBiliDownloader downloader = new BiliBiliDownloader();
public byte[] download(String filePath) throws InterruptedException {
if (map.containsKey(filePath)) {
System.out.printf("正在下載BiliBili檔案:%s%n", filePath);
return map.get(filePath);
}
byte[] res = downloader.download(filePath);
map.put(filePath, res);
return res;
}
}
public class YoutubeDownloader implements Downloader {
public byte[] download(String filePath) throws InterruptedException {
System.out.printf("正在下載Youtube檔案:%s%n", filePath);
// 模擬檔案下載,睡個10秒
Thread.sleep(10000);
return new byte[1024]; // 假裝是下載檔案的位元組陣列
}
}
public class ProxyYoutubeDownloader implements Downloader {
private static Map<String, byte[]> map = new HashMap<>();
private BiliBiliDownloader downloader = new BiliBiliDownloader();
public byte[] download(String filePath) throws InterruptedException {
if (map.containsKey(filePath)) {
System.out.printf("正在下載Youtube檔案:%s%n", filePath);
return map.get(filePath);
}
byte[] res = downloader.download(filePath);
map.put(filePath, res);
return res;
}
}
在客戶端呼叫時,引入Java反射,通過反射創建具體的代理物件,
在config.prop檔案中定義PROXY_NAME變數并指定需要反射創建的類的完整路徑,
public class Client {
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.load(new FileReader("src/resource/props/config.prop"));
Downloader downloader = (Downloader) Class.forName(prop.getProperty("PROXY_NAME"))
.getDeclaredConstructor().newInstance();
downloader.download("/root/home/buzuweiqi/java_manual.txt");
downloader = new ProxyYoutubeDownloader();
downloader.download("/root/home/buzuweiqi/linux_manual.txt");
}
}
通過Java反射機制,應對每次的需求變更,甚至都不需要修改客戶端代碼,只需要修改案例中的config.prop即可,減少了不必要的代碼修改,提高了系統的可維護性,
總結
優點
-
代理類與目標類的使用方式一致,這極大的降低了客戶端呼叫的學習成本,易用性高,
-
面向介面,無需在意實作的細節,
缺點
- 類的數量倍增,系統復雜度增加,
適用場景
- 當需要對于模塊拓展,但又不方便打破客戶端原有的呼叫規則時,客戶端中物件的創建依舊需要修改,這沒有什么好的辦法,
- 常用的代理模式使用方案
- 緩沖代理(案例)
- 遠程代理
- 虛擬代理
除此之外還有很多應用場景,代理模式是設計模式中使用非常廣泛的一種,
本文來自博客園,作者:spoonb,轉載請注明原文鏈接:https://www.cnblogs.com/spoonb/p/16735362.html
個人主頁:blogcafe.cn 比博客園更新速度更快,歡迎大家的光顧
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/528054.html
標籤:其他
上一篇:OpenGL ES EGL eglCreatePbufferSurface
下一篇:JAVA的File物件
