设计模式结构型模式二.docx
《设计模式结构型模式二.docx》由会员分享,可在线阅读,更多相关《设计模式结构型模式二.docx(31页珍藏版)》请在冰点文库上搜索。
设计模式结构型模式二
第5章结构型模式
(二)
5.1代理模式(Proxy)
Proxy代理模式是一种结构型设计模式,主要解决的问题是:
在直接访问对象时带来的问题,比如说:
要访问的对象在远程的机器上。
在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
如图5-1所示。
图5-1代理原理
比如说C和A不在一个服务器上,A要频繁的调用C,我们可以在A上做一个代理类Proxy,把访问C的工作交给Proxy,这样对于A来说,就好像在直接访问C的对象。
在对A的开发中我们可以把注意力完全放在业务的实现上。
5.1.1概念
1、名称
代理(Proxy)模式给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
代理模式的英文叫做Proxy或Surrogate,中文都可译成"代理"。
所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。
在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
2、结构
下面我们通过“高老庄悟空降八戒”的故事来描述代理模式的结构和原理。
【高老庄的故事】
却说那春融时节,悟空牵着白马,与唐僧赶路西行。
忽一日天色将晚,远远地望见一村人,这就是高老庄,猪八戒的丈人高太公家。
为了将高家三小姐解救出八戒的魔掌,悟空决定扮做高小姐,会一会这个妖怪:
“行者却弄神通,摇身一变,变得就如那女子一般,独自个坐在房里等那妖精。
不多时,一阵风来,真个是走石飞砂……那阵狂风过处,只见半空里来了一个妖精,果然生得丑陋:
黑脸短毛,长喙大耳,穿一领青不青、蓝不蓝的梭布直裰,系一条花布手巾……走进房,一把搂住,就要亲嘴……”
【高家三小姐的神貌和本人】
悟空的下手之处是将高家三小姐的神貌和她本人分割开来,这和“开一闭”原则有异曲同工之妙。
这样一来,“高家三小姐本人”也就变成了“高家三小姐神貌”的具体实现,而“高家三小姐神貌”则变成了抽象角色,如下图5-2所示。
图5-2“神貌”抽象角色
【悟空扮演并代替高家三小姐】
悟空巧妙地实现了“高家三小姐神貌”,也就是说同样变成了“高家三小姐神貌”的子类。
悟空可以扮演高家三小姐,并代替高家三小姐会见八戒,其静态结构图如图5-3所示。
图5-3悟空代替高家三小姐
悟空代替“高家三小姐本人”去会见猪八戒。
显然这就是代理模式的应用。
具体地讲,这是保护代理模式的应用。
只有代理对象认为合适时,才会将客户端的请求传递给真实主题对象。
【八戒分辨不出真假老婆】
从《西游记》的描述可以看出,猪八戒根本份辨不出悟空扮演的“高家三小姐替身”和“高家三小姐本人”。
客户端分辨不出代理主题对象与真实主题对象,这是代理模式的一个重要用意。
悟空代替高家三小姐会见八戒的对象图如图5-4所示。
图5-4对象图
3、意图
为其他对象提供一种代理以控制这个对象的访问。
4、角色
抽象主题角色:
声明了代理主题和真实主题的公共接口,使任何需要真实主题的地方都能用代理主题代替。
代理主题角色:
含有真实主题的引用,从而可以在任何时候操作真实主题,代理主题功过提供和真实主题相同的接口,使它可以随时代替真实主题。
代理主题通过持有真实主题的引用,不但可以控制真实主题的创建或删除,可以在真实主题被调用前进行拦截,或在调用后进行某些操作。
真实代理对象:
定义了代理角色所代表的具体对象。
5.1.3适用范围
代理的实质是为其他对象提供一种代理以控制对这个对象的访问。
在一下情况适合使用它:
如果那个对象是一个是很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,打开文档必须很迅速,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片。
如果那个对象在Internet的某个远端服务器上,直接操作这个对象因为网络速度原因可能比较慢,那我们可以先用Proxy来代替那个对象。
某个客户端不能直接操作到某个对象,但又必须和那个对象有所互动。
比如一个对象不具有网络访问功能,但有时必须访问网络中的其它对象。
5.1.4实现
在生活中,一个红酒厂商是不会直接把红酒零售客户的,都是通过代理来完成他的销售业务的。
而客户,也不用为了喝红酒而到处找工厂,他只要找到厂商在当地的代理就行了,具体红酒工厂在那里,客户不用关心,代理会帮他处理。
红酒代理商和红酒厂商都有销售红酒的只能,我们可以为他们定义一个共同的抽象主题角色。
结构如图5-5所示。
图5-5结构图
1、静态代理
抽象主题接口ISell代码如下:
packagecom.sanlian.mode.proxy;
/**
*代理主题接口、红酒销售接口
*@authorsanlian
*@versionNov26,20103:
44:
26PM
*/
publicinterfaceISell{
/**
*红酒销售方法
*@return
*/
Objectsell();
}
具体主题实现类RedWineFactory代码如下:
packagecom.sanlian.mode.proxy;
/**
*真实主题类,红酒工厂
*@authorsanlian
*@versionNov26,20103:
47:
20PM
*/
publicclassRedWineFactoryimplementsISell{
publicObjectsell(){
System.out.println("真实主题角色RedWineFactory被调用了");
returnnewObject();
}
}
代理主题实现类RedWineProxy代码如下:
packagecom.sanlian.mode.proxy;
/**
*代理目标类,红酒代理商
*
*@authorsanlian
*@versionNov26,20103:
48:
15PM
*/
publicclassRedWineProxyimplementsISell{
//持有一个RedWineFactory对象的引用
privateRedWineFactoryredWineFactory;
//销售总量
privatestaticintsell_count=0;
publicRedWineProxy(){
}
publicRedWineProxy(RedWineFactoryrwf){
super();
this.redWineFactory=rwf;
}
publicObjectsell(){
/*
*在通过代理主题角色我们可以在真实主题角色被调用前做一些诸如权限判断的事情
*/
if(checkUser()){
Objectobj=redWineFactory.sell();
//同样,在调用后我们也可以执行一些额外的动作
this.count();
returnobj;
}else{
thrownewRuntimeException();
}
}
/**
*记录销售数量
*/
privatevoidcount(){
sell_count++;
System.out.println("添加销售记录………………");
}
protectedbooleancheckUser(){
//dosomething
System.out.println("检查用户信息………………");
returntrue;
}
publicstaticintgetSell_count(){
returnsell_count;
}
publicRedWineFactorygetRedWineFactory(){
returnredWineFactory;
}
publicvoidsetRedWineFactory(RedWineFactoryredWineFactory){
this.redWineFactory=redWineFactory;
}
}
测试类代码如下:
packagecom.sanlian.mode.proxy;
publicclassTest{
publicstaticvoidmain(String[]args){
RedWineFactoryrwf=newRedWineFactory();
ISellsell=newRedWineProxy(rwf);
sell.sell();
}
}
运行结果如图5-6所示。
图5-6运行结果
2、动态代理
上面的代理,我们强迫代理类RedWineProxy实现了抽象接口ISell。
这导致代理类无法通用于其他接口,所以不得不为每一个接口实现一个代理类。
幸好,java为代理模式提供了支持。
java主要是通过Proxy类和InvocationHandler接口来给实现对代理模式的支持的。
下面用java的代理机制来实现上面的红酒例子。
动态代理类代码如下:
packagecom.sanlian.mode.proxy;
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
importjava.lang.reflect.Proxy;
/**
*动态代理类
*@authorsanlian
*@versionNov26,20104:
17:
21PM
*/
publicclassDynamicProxyimplementsInvocationHandler{
//代理目标对象
privateObjectproxy_obj;
protectedDynamicProxy(Objectobj){
this.proxy_obj=obj;
}
/**
*获取代理对象
*@paramobj
*@return
*/
publicstaticObjectfactory(Objectobj){
Classcls=obj.getClass();
//通过Proxy类的newProxyInstance方法来返回代理对象
returnProxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(),newDynamicProxy(obj));
}
/**
*实现InvocationHandler接口的invoke
*/
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
throwsThrowable{
System.out.println("函数调用前被拦截了:
"+method);
if(args!
=null){
//打印参数列表
System.out.println("方法有"+args.length+"个参数,分别是:
");
for(inti=0;iSystem.out.println("\n"+args[i]);
}
}
//利用反射机制动态调用原对象的方法
Objectmo=method.invoke(proxy_obj,args);
System.out.println("函数调用后进行处理:
"+method);
returnmo;
}
}
测试类如下:
publicclassTest{
publicstaticvoidmain(String[]args){
ISellsi=(ISell)DynamicProxy.factory(newRedWineFactory());
si.sell();
}
}
运行结果如图5-7所示。
图5-7动态代理运行结果
通过上面的代码可以看出,代理主题DynamicProxy类并没有实现我们定义的ISell接口,而是实现了java的InvocationHandler接口,这样就把代理主题角色和我们的业务代码分离开来,使代理对象能通用于其他接口。
其实InvocationHandler接口就是一种拦截机制,当系统中有了代理对象以后,对原对象(真实主题)方法的调用,都会转由InvocationHandler接口来处理,并把方法信息以参数的形式传递给invoke方法,这样,我们就可以在invoke方法中拦截原对象的调用,并通过反射机制来动态调用原对象的方法。
动态代理可以任意代理需要的类型,只需切换相应的代理逻辑即可(DynamicProxy中的invoke实现),如下测试代码:
importjava.util.ArrayList;
importjava.util.List;
publicclassTest{
publicstaticvoidmain(String[]args){
Listli=(List)DynamicProxy.factory(newArrayList());
intsize=li.size();
System.out.println("size:
"+size);
}
}
其运行结果如图5-8所示。
图5-8代理ArrayList对象
此动态代理技术也是Spring框架的aop编程的基础。
5.1.5分类
如果按照使用目的来划分,代理有以下几种:
远程(Remote)代理:
为一个位于不同的地址空间的对象提供一个局域代表对象。
这个不同的地址空间可以是在本机器中,也可是在另一台机器中。
远程代理又叫做大使(Ambassador)。
虚拟(Virtual)代理:
根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。
Copy-on-Write代理:
虚拟代理的一种。
把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
保护(ProtectorAccess)代理:
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
Cache代理:
为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
防火墙(Firewall)代理:
保护目标,不让恶意用户接近。
同步化(Synchronization)代理:
使几个用户能够同时使用一个对象而没有冲突。
智能引用(SmartReference)代理:
当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
在所有种类的代理模式中,虚拟(Virtual)代理、远程(Remote)代理、智能引用代理(SmartReferenceProxy)和保护(ProtectorAccess)代理是最为常见的代理模式。
5.2适配器模式(AdapterPattern)
在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。
那么如何应对这种“迁移的变化”?
如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?
这就是本文要说的Adapter模式。
5.2.1概念
1、名称
适配器模式(AdapterPattern)把一个类的接口变换成客户端所期待的另一中接口,从而是原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
又称为转换器模式、变压器模式、包装(Wrapper)模式(把已有的一些类包装起来,使之能有满足需要的接口)。
将两个不兼容的类纠合在一起使用,属于结构型模式,需要有Adaptee(被适配者)和Adapter(适配器)两个身份。
2、结构
适配器模式又分为类适配器模式和对象适配器模式。
结构分别如图5-9、5-10所示。
图5-9类适配器结构
图5-10对象适配器结构
3、意图
将一个类的接口转换成客户希望的另外一个接口。
Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
4、适用性
你想使用一个已经存在的类,而它的接口不符合你的需求。
你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
(仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。
对象适配器可以适配它的父类接口。
5、适配器模式中的各个角色
目标(Target)角色:
这是客户所期待的接口。
目标可以是具体的或抽象的类,也可以是接口。
源(Adaptee)角色:
需要适配的类。
适配器(Adapter)角色:
通过在内部包装(Wrap)一个Adaptee对象,把源接口转换成目标接口。
5.2.2作用
在实际开发过程中,我们经常遇到这样的事情,我们根据初步的需求制定了一个基类,在开发过程中才了解到详细的需求或者需求发生了变动。
而开发工作中的接口早已经定义完毕,并已经大规模投入编码。
此时若改动接口的定义会造成很多编码上重复性的修改工作,并进而有可能造成修改不完全而导致的语义错误或逻辑错误。
语义错误尚可以在编译阶段发现,而一旦发生逻辑性的错误,后果将会非常严重,甚至足以导致系统崩溃。
此时就需要用到适配器模式的设计方法。
我们经常碰到要将两个没有关系的类组合在一起使用,第一解决方案是:
修改各自类的接口,但是如果我们没有源代码,或者,我们不愿意为了一个应用而修改各自的接口。
怎么办?
使用Adapter,在这两个接口之间创建一个混合接口。
5.2.3适用范围
适配器模式主要应用于,当接口里定义的方法无法满足题客户的需求,或者说接口里定义的方法的名称或者方法界面与客户需求有冲突的情况。
在以下各种情况下使用适配器模式:
系统需要使用现有的类,而此类的接口不符合系统的需要。
想要建立一个可以重复使用的类,用于与一些批次之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
这些源类不一定有很复杂的接口。
类或方法的作用相同但名称不同的类或方法之间进行适配。
5.2.4实现
实现Adapter模式,有两种方式:
组合(composition)和集成(inheritance)。
假设我们要打两种类型的桩,分别是方形桩和圆形桩。
/**
*方形桩
*@authorsanlian
*@versionNov22,20101:
48:
27PM
*/
publicclassSquarePeg{
publicvoidinsert(Stringstr){
System.out.println("SquarePeginsert():
"+str);
}
}
/**
*圆形桩
*@authorsanlian
*@versionNov22,20101:
50:
03PM
*/
publicclassRoundPeg{
publicvoidinsertIntoHole(Stringmsg){
System.out.println("RoundPeginsertIntoHole():
"+msg);
}
}
现在有一个应用,需要既打方形桩、又打圆形桩。
那么我们需要将这个没有关系的类综合应用。
假设RoundPeg我们没有源代码,或我们不想修改源代码,那么我们使用Adapter来实现这个应用,代码如下:
packagecom.sanlian.mode.adapter;
/**
*适配器模式
*@authorsanlian
*@versionNov22,20101:
53:
36PM
*/
publicclassPegAdapterextendsSquarePeg{
privateRoundPegroundPeg;
publicPegAdapter(RoundPegrpeg){
this.roundPeg=rpeg;
}
publicvoidinsert(Stringstr){
super.insert(str);
this.roundPeg.insertIntoHole(str);
}
}
在上面代码中,RoundPeg属于Adaptee(被适配者)。
PegAdapter是Adapter(适配者),将Adaptee(被适配者RoundPeg)和Target(目标SquarePeg)进行适配。
实际上这是将组合犯法(coposition)和继承(inheritance)方法综合运用。
PegAdapter首先继承SquarePeg,然后使用new的组合生成对象方法,生成RoundPeg的对象roundPeg,再集成父类insert()方法。
从这里,您也了解使用new生成对象和使用extends集成生成对象的不同,前者无需对原来的类修改,甚至无需要知道其内部结构和源代码。
5.2.5进一步使用适配器模式
上面的PegAdapter是继承了SquarePeg,如果我们需要两边集成,将集成SquarePeg又继承RoundPeg,因为Java中不允许许多集成,但是我们可以实现(implements)两个接口(interface)。
/**
*圆形桩接口
*@authorparddu
*@versionNov22,20102:
09:
06PM
*/
publicinterfaceIRoundPeg{
publicvoidinsertIntoHole(Stringmsg);
}
/**
*方形桩接口
*@authorsanlian
*@versionNov22,20102:
12:
05PM
*/
publicinterfaceISquarePeg{
publicvoidinsert(Stringstr);
}
下面是新的RoundPeg和SquarePeg,除了实现接口这一区别,和上面的没什么区别。
/**
*圆形桩
*@authorsanlian
*@versionNov22,20101:
50:
03PM
*/
publicclassRoundPegimplementsIRoundPeg{