【设计模式】牛掰格拉斯的代理模式
代理的本质
无论任何时候,只要谈到设计模式,大脑中一定要蹦出这四个字“活学活用”。
要想对某个事物做到活学活用,必须要对它足够了解,甚至要剖析到本质才行。
总是会有些人说,我干嘛要知道原理,干嘛要去看源码?会用就行了。对于这种情况,我只有五个字相送,“你开心就好”。
不可否认,认识一个陌生事物,大部分情况还是要从定义开始。
代理模式:为某对象提供一种代理以控制对该对象的访问,从而限制、增强或修改该对象的一些特性。
如果对代理模式本身就很熟悉的人,一眼就明白什么意思,甚至连代码怎么写都会浮现在脑海中。
关键是对代理模式一点都不熟悉的人,看到定义后绝对一脸懵。下面通过简单的图形来揭开迷惑。
没有使用代理模式,如下图01:
“源”直接访问“目标”。
使用了代理模式,如下图02:
“源”访问代理,代理访问“目标”。
人群中一定会有两种声音:
1)设计模式是很高深莫测的东西,有这么简单吗?
2)这怎么没有代码实现啊?
来听听作者的看法:
1)高深莫测和简单不一定都是对冲的。幸福绝对是高深莫测的,那什么是幸福呢,最多的答案恐怕就是,“一亩土地两头牛,老婆孩子热炕头”。
多么简单朴实的答案,可惜,包括我在内的很多人都追求不到。主要原因是我们人为(主观)的把很多事情搞复杂了。下面这个事情可以说明这一点。
中秋节放假时到附近的一个小景区去看一看,我发现有一种果树的果子挂满枝头,又大又圆,让人看了之后特别有欲望,甚至垂涎欲滴,可惜没有一个人去摘。
我的第一反应就是果子肯定不能吃。过了一会儿,终于抑制不住好奇心,就找了一个比较矮的果树,摸了摸枝头上的果子。果然硬如磐石。
2)对于一上来就说代码实现的人,只能认为是你拥有了一把锤子之后,看什么都像钉子。
代码实现永远都是最后一步,但在它之前,要找出问题,分析问题,给出方案,论证方案可行性。如果这些都OK了,代码就是水到渠成的事了。
记住这句话:
认清原理,搞清本质,永远都是最重要的,不单单是在写代码上,在社会上依然如此。
那代理的本质是什么呢?就两个字,“加层”,即增加一(多)层。这就是本质。
像其它的什么“控制访问”啦,“增强或修改特性”啦,只不过是这个“层"产生的一些(副)作用罢了。
生活中充满着代理
虽然大部分人都没有参与过诉讼,但作为常识我们都知道,当事人可以通过协议把自己的一些权力授权给律师,律师就可以在法庭上行使这些权利,此时律师就是代理。
现在网购已成为生活的一部分,但是收快递却比较麻烦,因为通常家中无人。此时菜鸟驿站(或妈妈驿站)出现了,它帮我们签收和暂存包裹,所以它就是代理。
现在生活压力大,每个人都要上班,所以中午和下午都没有时间去接送自己上小学的孩子,此时只能选择午托,我们交了托费之后就等于给了它授权,它代替我们去接送孩子,可见午托也是代理。
还有各种产品的代理商,有大区代理,省级代理,市级代理,等等。还有就是微商/代购,微商自己没有货品,只是发发朋友圈,最终是别人发货。代购就更直接了,代替你去购买,然后再邮寄给你。
明星艺人都有自己的经纪人,可以替自己接一些活动,讨价还价,安排日程等。此时经纪人就是代理。还有大BOSS也会请一到多个秘书,来代替自己做一些事情。
可见,生活中充满着很多代理,他们以各种各样的形式存在着,发挥着各种各样的功能和作用,也确实解决了很多社会和生活的问题。
但从本质来看,代理大都以“层”的形式呈现,站在老板的前面,替老板做事情。
和计算机相关的代理
大部分人可能都听过这样一句话,凡是遇到不好解决的问题,大都可以通过加一层得到有效的解决。加的这个层很多时候和代理有关。
为了网络安全问题,可以加一层防火墙,防火墙虽然不完全是代理,但却用到许多代理的理论和技术。
为了扩充本地局域网络,可以加一层交换机或路由器,它用来代理和转发网络请求。还会有一些附带的其它功能。
为了平衡多个服务器的处理能力,我们在前面加一个请求路由层,也就是负载均衡器了,如Nginx,它可以代理请求,并按规则转发。
再说说CRUD,原来是我们写代码直接使用JDBC访问数据库,现在我们写代码使用的是ORM框架,ORM框架再使用JDBC去访问数据库。ORM框架可以看作是JDBC的代理。
我们可以看到,从硬件到中间件,再到程序框架,都有代理的影子。
和编程相关的代理
上面所说的代理,都是广义的代理,主要侧重于角色和功能。
一旦在编程中谈到代理,基本就是狭义代理了。除了广义代理的要求外,还要保持类型的兼容和“接口”的一致。
说白了就是需要被代理对象的地方,给它一个代理也可以。可以在被代理对象上调用的方法,在代理上也可以调用。
这个要求和代理模式中的要求是一样的。
如果被代理的对象是一个类。我们用Target表示。
class Target {
public String getDateTime() { return "2019-10-09" }
}
为了保持类型兼容和接口一致,我们需要生成一个子类。
除此之外还要有被代理的对象,所以还需一个成员变量。
为了添加一些功能,通常需要重写一些方法。
class Proxy extends Target {
private Target target;
@Override public String getDateTime() { return target.getDateTime() + ", 星期三"; }
}
这样就生成了一个代理,在需要Target的地方换成Proxy也没有问题,而且还会在日期后面加上星期。
如果被代理的对象是一个接口。我们用ITarget表示。
interface ITarget {
String sayHello(String name);
}
class Target implements ITarget {
@Override public String sayHello(String name) { return "hello " + name; }
}
此时我们只需实现接口即可(当然也可以生成子类),其它的保持不变。
class Proxy implements ITarget {
private ITarget target;
@Override public String sayHello(String name) { return target.sayHello(name) + ", long time no see."; }}
这也是一个代理,同样可以使用Proxy代替Target,而且在原来问好的基础上增加了更多的话语。
由于类型兼容且接口一致,所以用户代码有时也不知道到底是对象本身还是它的代理,不过这通常并不重要。
代理的好处我们已经看到了,但是也有不好的地方,就是要写代理的代码,造成代码量增加。
这个问题已经通过动态代理解决了。在Java里比较有名的动态代理,就是JDK动态代理和CGLIB代理。这大家都知道了。
全文总结:
代理的本质就是通过加一层来解决问题。类型兼容和接口一致只是限制条件而已。
代理有着广泛的应用,想想Spring的成功,代理贡献了多少,绝对功不可没。
仔细体会下“加层”的含义,在代码中和生活中,你会发现它真的很牛掰格拉斯。
>>> 玩转SpringBoot系列文章 <<<
【玩转SpringBoot】用好条件相关注解,开启自动配置之门
>>> 品Spring系列文章 <<<
品Spring:SpringBoot和Spring到底有没有本质的不同?
品Spring:SpringBoot轻松取胜bean定义注册的“第一阶段”
品Spring:SpringBoot发起bean定义注册的“二次攻坚战”
品Spring:注解之王@Configuration和它的一众“小弟们”
品Spring:对@PostConstruct和@PreDestroy注解的处理方法
品Spring:对@Autowired和@Value注解的处理方法
品Spring:真没想到,三十步才能完成一个bean实例的创建
品Spring:关于@Scheduled定时任务的思考与探索,结果尴尬了
>>> 热门文章集锦 <<<
爸爸又给Spring MVC生了个弟弟叫Spring WebFlux
【面试】吃透了这些Redis知识点,面试官一定觉得你很NB(干货 | 建议珍藏)
【面试】如果你这样回答“什么是线程安全”,面试官都会对你刮目相看(建议珍藏)
【面试】迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)
【面试】一篇文章帮你彻底搞清楚“I/O多路复用”和“异步I/O”的前世今生(深度好文,建议珍藏)
作者是工作超过10年的码农,现在任架构师。喜欢研究技术,崇尚简单快乐。追求以通俗易懂的语言解说技术,希望所有的读者都能看懂并记住。下面是公众号的二维码,欢迎关注!