對使用者端隱藏目標類,建立代理類拓展目標類,並且對於使用者端隱藏功能拓展的細節,使得使用者端可以像使用目標類一樣使用代理類,面向代理(使用者端只與代理類互動)。
話不多說,看一個優化案例。
目前的功能是下載可以下載檔案。
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");
}
}
下載工具類對使用者端完全暴露,使用者端可以直接使用下載類實現下載,這實際上是無可厚非的。
經過研究發現,這個下載類有一個問題:每次呼叫都肯定會下載新的檔案,即便檔案已經被下載過。
為了解決這個問題,開發團隊經過商討已經有了一個初步的方案。看一下程式碼樣例。
團隊決定使用傳統的修改方式(直接修改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的檔案。
為了實現這個需求,以及方便以後同類的需求變更,是時候用上代理模式。
代理模式在使用的時候需要頂一個一個頂層介面,並且使得代理類和被代理類都實現這個介面。
代理類中需要持有非代理類的一個物件。並且在呼叫代理類的功能前後可以根據業務需要拓展新的功能。
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");
}
}
使用者端不再依賴目標類,而是轉而依賴代理類。
代理模式使得增加相似需求時可以只增加一對實現類(目標類,代理類),而不用修改原本的類,符合開閉原則。
實際上通常我們會使用一個更為簡單的方式控制代理物件的建立:反射。
高層介面,實現的目標類、代理類依舊不變。
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 比部落格園更新速度更快,歡迎大家的光顧