当前位置:首页 > CN2资讯 > 正文内容

设计模式---代理模式

2天前CN2资讯

简述

对客户端隐藏目标类,创建代理类拓展目标类,并且对于客户端隐藏功能拓展的细节,使得客户端可以像使用目标类一样使用代理类,面向代理(客户端只与代理类交互)。

话不多说,看一个优化案例。

优化案例

最初版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​​即可。减少了不必要的代码修改,提高了系统的可维护性。

总结

优点

  • 代理类与目标类的使用方式一致,这极大的降低了客户端调用的学习成本,易用性高。
  • 面向接口,无需在意实现的细节。

缺点

  • 类的数量倍增,系统复杂度增加。

适用场景

  • 当需要对于模块拓展,但又不方便打破客户端原有的调用规则时。客户端中对象的创建依旧需要修改,这没有什么好的办法。
  • 常用的代理模式使用方案
  • 缓冲代理(案例)
  • 远程代理
  • 虚拟代理

除此之外还有很多应用场景,代理模式是设计模式中使用非常广泛的一种。

    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/24809.html

    分享给朋友:

    “设计模式---代理模式” 的相关文章

    选择合适的服务器购买攻略:性能、预算与品牌分析

    在购买服务器之前,进行充分的准备至关重要。首先,我喜欢明确自己购买服务器的目的。是否只是用来搭建网站,还是用于复杂的数据处理,抑或是作为云计算的基础设施?这些需求会直接影响我的选择。明确目标后,我可以更好地针对我的具体需求进行规划。 接着,我必须考虑预算。无论是想购买入门级的服务器,还是高性能的旗舰...

    Windows SSH Client安装与配置指南

    在Windows 10版本1809及以后的版本中,微软引入了OpenSSH客户端,这让很多用户的远程管理变得更为便捷。作为一个IT爱好者,我发现这个特性非常有用,它让我能够轻松地通过SSH协议安全地连接和管理远程服务器。接下来,我将分享一些Windows SSH客户端的安装和配置过程,方便大家快速上...

    如何选择合适的免费VPS服务并有效利用

    免费VPS概述 在研究云计算相关技术的时候,VPS(虚拟专用服务器)成了一个非常重要的概念。简单来说,VPS是一种通过虚拟化技术来划分的服务器,每个VPS都是独立的,用户可以获得与一个物理服务器类似的操作体验。作为个人开发者或中小企业的选择,VPS提供了灵活性和可控性,是许多人搭建网站或开发项目的理...

    RackNerd IP测评:选择可靠VPS的最佳指南

    在我接触过的众多VPS服务提供商中,RackNerd以其高性价比的特点脱颖而出。作为一家位于美国的公司,RackNerd专注于为用户提供可靠的虚拟私人服务器(VPS)解决方案。在这里,我将和大家分享一些关于RackNerd的重要信息,尤其是它的IP测评,我认为这对想要选择VPS的用户来说至关重要。...

    续费同价服务器:云服务的透明定价策略与用户优势

    续费同价服务器是什么呢?说白了,就是云服务提供商在定价上采取的一种政策。无论是新用户第一次购买,还是老用户续费,价格都是一样的。这种做法让很多用户感到安心,不用担心下次续费时价格会大幅上涨。这一策略在云服务行业越来越受到重视,也给用户带来了不少好处。 首先,续费同价服务器让价格变得透明。我之前在选择...

    Virmach Coupons: 轻松获取超值优惠,优化你的VPS选择

    Virmach成立于2014年,作为一家美国VPS服务商,在业内享有良好的声誉。它的总部位于加利福尼亚州洛杉矶,正是这样得天独厚的地理位置让它能迅速成长并服务全球用户。到现在为止,Virmach已经发展成为一家提供各种配置和价格方案的服务商,特别以低价VPS而闻名,吸引了大量希望降低运营成本的个人和...