设计模式学习教程:代理模式
2023-10-11 03:33
代理是指代表委托人管理,以他人的名义,代表委托人处理超出自己职责或力所能及的事务,达成合作关系并促成更高效地完成事务。 。例如,名人经纪人不知道如何像名人一样唱歌、跳舞或表演。相反,他们为明星处理一些自己无暇顾及的事务(这并不意味着他们可以代理非常事情),比如促销宣传、合同谈判等。达成合约后,他们会通知明星演出。另一个例子是机票销售代理商既不制造飞机也不提供飞行服务。他们只负责卖票。律师不会因胜诉而获得赔偿,也不会因败诉而受到法律制裁。他们只负责代表人打官司等等。
图片来源网络,侵权删除
生活中还有很多这样的例子,但对于我们这些从事技术工作的人来说,我相信以网络代理为例是最合适的。
首先,我们在能够接入互联网之前,必须先到互联网服务提供商(ISP)申请互联网宽带服务,所以顺理成章的获得光纤入户并获得调制解调器,也就是俗称的调制解调器。作为“猫”。好了,“猫”实现了上网接口,看代码。
1public interface Internet{//上网接口
2
3 public void access(String url);
4
5}
1public类Modem实现Internet {//Modem
2
3 @覆盖
4 public void access(String url){//实现上网接口
5 System.out.println("访问:" + url);
6 }
7}
作为调制解调器,它必须具有互联网接入功能。用户的电脑只需要用网线连接这只“猫”就可以上网。真有这么简单吗?然而有一天,我们发现孩子们总是在学习的时候偷偷上网看电影、玩游戏,于是我们决定对某些网站进行过滤,防止色情、赌博、毒品对未成年人的伤害。然后,我们需要在客户端电脑和猫之间添加一层代理来过滤一些不良网站。最后,我们决定购买一个带有过滤功能的路由器。
1public class RouterProxy实现Internet{//路由器代理类
2
3 private Internet modem;//持有代理类的引用4 private List blackList = Arrays.asList("电影", "游戏", "音乐", "小说");
5
6 公共 RouterProxy() {
7 this.modem = new Modem();//实例化代理类
8 System.out.println("拨号上网...连接成功!");
9 }
10
11 @覆盖
12 public void access(String url) {//同样实现上网接口方法
13 for (String keywords: blackList) {//循环黑名单
14 if (url.contains(keyword)) {//是否包含黑名单词
15 System.out.println("禁止访问:" + url);
16返回;
17}
18}
19 modem.access(url);//正常上网
20}
21}
注意,这里的路由器代理主要起到代理的作用。与之前的“猫”一样,它也实现了互联网接口。看似具有上网功能,其实不然。第12行代码从一开始就实现了对上网功能的过滤。如果该地址包含黑名单敏感词,将被禁止访问,直接退出。否则,第19行就会调用“猫”的上网方法,你看,最后调用的是“猫”的上网函数。注意,这里为了控制“猫”,特意为此创建了代理。我们直接在第7行实例化它,而不需要其他人来注入它。好了,孩子现在已经上线了,迫不及待地想运行一下。
1公共类客户端{
2 公共静态无效主(字符串[] args){
3 互联网代理 = new RouterProxy(); //代理被实例化
4 proxy.access("http://www.moviwww.hack95.com");5 proxy.access("http://www.hack95.com");
6 proxy.access("ftp://www.学.com/java");
7 proxy.access("http://www.hack95.com");
8
9 /* 运算结果
10 拨号上网...连接成功!
11 禁止访问:http://www.moviwww.hack95.com
12 禁止访问:http://www.hack95.com
13 访问:ftp://www.hack95.com/java
14 访问:http://www.hack95.com
15*/
16}
17}
在第 3 行中,子进程不再被实例化为“cat”,而是被路由器代理所取代。也就是说,每个在线的人都是连接到路由器上的,而不是直接连接到“猫”上的。这不仅为我们省去了拨号的麻烦(路由器帮助拨号),而且孩子们也无法再访问杂乱的网站。事实上,这个代理本身并不具备访问互联网的能力。它只是调用“猫”上网功能。它存在的目的只是为了控制“猫”的互联网访问并充当它的代理。
说到这里,大家有没有发现,这种代理模式和装饰器模式很相似呢?如果你观察UML类图之间的关系,你会发现它们几乎是一模一样的。那么这个模式有什么意义呢?事实上,代理模式更强调对代理对象的控制,而不是局限于装饰目标对象、增强其原有功能。就像明星的例子一样,如果钱不够,合同达不成,明星就不允许随意炫耀。
相信大家都有清楚的认识。这也是我们最常用的代理模式。其实还有一种叫做动态代理。不同的是,它的实例化过程是在运行时完成的,这意味着我们不需要专门为某个接口编写这样的代理类,而是根据接口动态生成的。
例如,让我们忘记之前的路由器代理。当我们内网的上网设备越来越多的时候,路由器的Lan口已经满了,不够用了,所以我们决定换成交换机,看一下代码。
1public interface Intranet {//LAN接入接口
2
3 public void fileAccess(字符串路径);
4
5}为了简单起见,我们假设这个交换机Switch实现了LAN访问接口Intranet。请注意,这不是互联网接口。
1public类Switch实现内网{
2
3 @覆盖
4 公共无效文件访问(字符串路径){
5 System.out.println("访问内网:" + 路径);
6 }
7
8}
这里发生的是局域网文件访问,例如在另一台内网机器上复制共享文件,我们要保证和以前一样的关键字过滤控制功能,也就是说,无论是哪个地址,都必须首先进行过滤。如何重复利用?
我们在这里想一下。猫实现互联网接入接口,交换机实现局域网接入接口。那么我们应该如何编写我们的过滤代理类呢?是实现Internet接口还是内网接口?或者两者都做?当添加新的类接口时,我们是否需要不断更改实现类?这显然是行不通的。过滤器无非是一段过滤逻辑,不需要来回改变。这违反了设计模式的开闭原则。动态代理应运而生,我们看一下代码。
1public class KeywordFilter 实现 InvocableHandler {
2
3 private List blackList = Arrays.asList("电影", "游戏", "音乐", "小说");
4
5 // 被代理的真实对象,无论是猫、开关还是其他东西。
6 私有对象起源;
7
8 公共关键字过滤器(对象起源){
9 this.origin = origin;//注入代理对象
10 System.out.println("开启关键字过滤模式...");
11}
12
13 @覆盖
14 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
15 //业务逻辑切入方法方面之前16 字符串arg = args[0].toString();
17 for (字符串关键字:blackList) {
18 if (arg.toString().contains(关键字)) {
19 System.out.println("禁止访问:" + arg);
20 返回 null;
21 }
22 }
23 // 调用真正的代理对象方法
24 return 方法.invoke(origin, arg);
25}
26
27}
对于这个关键字过滤功能,我们不再写到代理类中,而是另外写一个类,实现JDK反射包中提供的InvocableHandler接口,并在第9行注入要代理的对象,不管是猫还是猫转变。始终是一个Object,然后在第14行实现了invoke调用方法,以后会调用生成的动态代理来运行这个逻辑。显然,在执行真正的对象方法之前,我们在这里仍然保持相同的逻辑。运行过滤逻辑来控制。由于传入的参数是代理对象的method方法和一堆参数args,所以请注意,这里第24行我们必须使用反射来调用代理对象的origin。最后,让我们看看如何运行它。
1公共类客户端{
2 公共静态无效主(字符串[] args){
3
4 //访问外网(Internet),生成猫代理。
5 互联网 互联网 = (互联网) Proxy.newProxyInstance(
6 Modem.class.getClassLoader(),
7 Modem.class.getInterfaces(),
8 new KeywordFilter(new Modem()));9 internet.access(“http://www.moviwww.hack95.com”);
10 internet.access("http://www.hack95.com");
11 互联网.access(“http://www.hack95.com”);
12 Internet.access(“http://www.hack95.com”);
13
14 //访问内网(LAN)并生成交换机代理。
15 内网内网=(内网)Proxy.newProxyInstance(
16 Switch.class.getClassLoader(),
17 Switch.class.getInterfaces(),
18 new KeywordFilter(new Switch()));
19 Intranet.fileAccess("\\192.68.1.2\Shared\Movies\www.hack95.com4");
20 Intranet.fileAccess("\\192.68.1.2\Shared\Game\Hero.exe");
21 Intranet.fileAccess("\\192.68.1.4\shared\Java学习资料.zip");
22 Intranet.fileAccess("\\192.68.1.6\Java知音\设计模式.doc是什么鬼");
23
24 /*
25 开启关键字过滤模式...
26 禁止访问:http://www.moviwww.hack95.com
27 禁止访问:http://www.hack95.com
28 访问:http://www.hack95.com
29 访问:http://www.hack95.com
30 开启关键字过滤模式...31 禁止访问:\192.68.1.2 共享电影 www.hack95.com4
32 禁止访问:\192.68.1.2 共享游戏 Hero.exe
33 访问内网:\192.68.1.4sharedJava学习资料.zip
34 访问内网:\192.68.1.6Java知音设计模式是什么鬼.doc
35
36*/
37}
38}
可以看到,无论我们是访问互联网还是局域网,我们只需要生成对应的代理并调用即可,执行相同的过滤逻辑。这样我们就不需要再写任何代理类了。我们只需要实现一次 IncationHandler 就可以一劳永逸了。代理可以在运行时动态生成,以达到兼容任意接口的目的。
事实上,动态代理模式在很多框架中都有广泛的应用,比如spring的面向切面的AOP。我们只需要定义一个切面类@Aspect,并声明它的入口点@Pointcut(被代理的对象的哪些方法,也就是这里的cat和switch的access和accessFile),以及切入的代码块(要添加的逻辑,比如这里的过滤功能代码,可以分为预执行@Before、后执行@After、异常处理@AfterThrowing等),所以框架自动为我们生成了agent并进行了裁剪进入目标执行。就像每个方法前后添加日志的例子,或者更经典的事务控制的例子,在所有业务代码之前切换到“事务启动”,执行完之后再切换到“事务提交”。如果抛出异常并被捕获,则执行“事务”。 “回滚”,这样就不需要在每个业务类中都写这些重复的代码了。一劳永逸,冗余代码大大减少,开发效率惊人提升。
图片来源网络,侵权删除
他没有耳朵去听窗外发生的事情,只读圣贤之书。毕竟天地如山,山外的事情还是交给专家吧。
版权声明:本文内容由网友自愿贡献,本文所表达的观点仅代表作者自己的观点。本网站仅提供信息存储空间服务,不拥有任何所有权,也不承担相关法律责任。如果您发现本站有任何涉嫌侵权/非法内容,请发送邮件举报。一经核实,该网站将立即删除。
本文由斑马博客整理。本文链接为:https://www.hack95.com/index.php/post/2403.html