设计模式——借助代理模式体验"间接的美"(一
引言
在古代《三国志·诸葛亮传》中有这么一句话——“政事无巨细咸于亮。”;在唐代张九龄的《谢赐大麦面状》中还有这么一句——“勤俭于生人,事必躬亲,动合天德两个典故连起来就是“事无巨细,事必躬亲”,意思是无论大事小事都要亲自过问,亲力亲为,虽然这样的精神及其可贵。但在我们当代的生活中如果我们皆事必躬亲的话,那么我们的生活将会是十分疲倦的,毕竟任何人的精力都是有限的,每一个人或事都有自己擅长的事或工作,同样地我们的程序也是如此,每一个业务类都应该只专注于核心功能,而把其他繁杂的功能外包出去给代理类,比如说我们找房子,我们核心主题就是找到房子租下,如果我们自己找的话,得一家家联系,商讨最后决定价格、签约付款,而采用代理模式的话就让代理者去完成这些繁琐的细节,作为客户端我们甚至不需要知道中间的细节。
一、代理模式概述
代理模式(Proxy Pattern)又叫委托模式属于是一个使用率非常高结构型设计模式。其定义如下:为其他对象提供一种代理以控制对这个对象的访问。(Provide a surrogate or placeholder for another object to control access to it.)通俗可以理解成代理代理,就是帮你打理。你只跟代理一个人打交到。而不关心实际操作的人的具体如何做。代理模式目前框架里用的最多,主要作用是程序本身不关心被代理的身份细节。而只关心它暴露出来的共有行为接口,这个代理之所以理解困难,是有时候它像个工厂模式,有时候它像适配模式。也可从字面理解就是代理你去执行调用别的类的方法面向被调用的类。所以代理模式一般都会涉及到四个角色:抽象主题、继承或实现抽象主题的真实主题、代理类、客户类。
-
抽象主题——负责声明真实主题和代理的共同接口方法,同样的可以定义为接口或抽象类
-
继承或实现抽象主题的真实主题——定义了代理所表示的真实对象,由其执行具体的业务逻辑方法,也就是说当客户类通过代理类去执行操作时,本质就是调用真实主题的方法,所以这个类又被称为被委托类或被代理类。
-
代理类——该类会持有真实主题类的引用,并在其所实现的接口方法中完成对真实主题类的相关方法的调用(即为其他对象提供一种代理以控制对这个对象的访问),所以这个类又被称为委托类或代理类。
- 客户类——你前面这个类构建出了代理模式,总得有人使用吧,这个所谓的客户类,简单来说就是你使用代理模式的那个类。
二、代理模式的优点和缺点及可用场景
1、代理模式的共同优点
-
能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
-
客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
-
远程代理为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
-
虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
-
缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
-
保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
2、代理模式的缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
- 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。
3、适用场景
当无法或者不想直接访问某个对象或访问直接某个对象消耗巨大时,可以采取通过一个代理对象来间接访问,为了保持对客户端透明,代理对象和被代理对象需要实现相同的接口。
-
当客户端对象需要访问远程主机中的对象时可以使用远程代理。
-
当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
-
当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
-
当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
-
当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。
三、代理模式的实现形式
通常按照代码机制可以分为静态代理和动态代理两大类,而按照使用范围可以分为以下常用的四种:远程代理(Remote Proxy)、虚拟代理(Virtual Proxy)、保护代理(Protection Proxy)和智能引用(Smart Reference,又叫计数代理),值得注意的是两大分类机制并不独立,就是说无论是静态代理还是动态代理都可以采用以上四种形式,实现起来也很简单,无论是什么形式步骤和基本思想都大同小异,以法律代理简化流程为例。
1、静态代理
1.1、根据具体业务定义自己的抽象主题
可以定义成抽象类也可以定义成接口。
package proxy;
public interface ILegalSuit {
void submit();//提交诉讼申请
void proof();//进行举证
void debate();//法庭辩护
}
1.2、继承或实现自抽象主题的真实主题
真实主题其实就是被代理对象。
/**
*
* @author cmo
*诉讼人,本质上这些诉讼流程的具体事宜都是由诉讼人去定义实现的
*/
public class LegalSuiter implements ILegalSuit {
public void submit() {
System.out.println("起草诉讼书并提交至法院");
}
public void proof() {
System.out.println("采集证据并向法院举证");
}
public void debate() {
System.out.println("庭内辩护");
}
}
1.3、继承或实现自抽象主题的代理对象
实现代理对象主要步骤可以分为四步,第一,继承或实现自抽象主题,第二,持有被代理对象的引用并通过构造方法传入,第三,通过引用完成对被代理对象方法的调用,四,根据业务新增自己的逻辑。
package proxy;
/**
*
* @author cmo
*诉讼代理类,一般代理类的方法实现很简单就是持有被代理对象,
*并且实现抽象主题的方法里使用被代理对象的引用调用被代理类的方法,
*当然代理类中还可以定义其他的方法逻辑,也可以在调用被代理对像的方法前后增删逻辑
*/
public class LegalSuiterProxyer implements ILegalSuit {
private ILegalSuit suiter;
//通过构造方法把被代理对象传递过来
public LegalSuiterProxyer(ILegalSuit suiter){
this.suiter=suiter;
}
@Override
public void submit() {
suiter.submit();
}
@Override
public void proof() {
suiter.proof();
}
@Override
public void debate() {
suiter.debate();
}
}
测试
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
ILegalSuit suiter=new LegalSuiter();//首先构造一个被代理对象
LegalSuiterProxyer proxyer=new LegalSuiterProxyer(suiter);//创建一个与被代理对象绑定的代理对象
proxyer.submit();
proxyer.proof();
proxyer.debate();
}
}
前面说过代理模式又叫做委托模式,因为代理模式本质上就是一个委托机制,被代理对象将方法的执行委托给代理对象,客户端想要去完成相关操作的时候不需要直接去调用被代理对象,只需要简单地去找代理对象即可,而且扩展性也很高,比如说某天前代理人的诉讼已经完成了,那么代理人不可能也不工作了吧,他还想帮别人代理诉讼的话,采用代理模式就显示出很大的优势,要做的只是再新增一个被代理对象并传入到代理对象中即可,完全不会影响到前代理人的逻辑,高度解耦。那么何谓静态代理呢,从上例可以得出,代理对象和被代理对象并不是1:1的关系,代理对象可以代理多个相似的被代理对象,他们之中有一个明显特征:在我们客户端代码工作前,被代理对象的代码已知(是由程序员自己开发或者第三方自动生成),通俗来说就是在编码阶段我们就已经知道了这个代理类将代理谁。,这就是静态代理。
2、动态代理
而动态代理则与静态代理相反,在编码阶段我们不知道被代理对象是谁,被代理对象是在运行阶段动态生成的(一般是通过反射机制),动态代理的实现十分简单,因为JDK 自身已经为我提供了一个便捷的动态代理接口InvocationHandler,我们只需要实现该接口并重写invoke方法即可,前面步骤和静态代理差不多(略),区别在代理类的实现。
- 实现动态代理接口InvocationHandler
- 持有被代理对象的引用,由于被代理对象未知,所以使用Object类型
- 通过构造方法传递被代理对象的引用
- 重写invoke方法,并在方法中通过反射调用被代理类的方法
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxyer implements InvocationHandler {
private Object obj;//被代理对象的引用
public DynamicProxyer(Object obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=method.invoke(obj, args);//调用被代理对象的方法
return null;
}
}
测试
import java.lang.reflect.Proxy;
public class DynamicClient {
/**
* @param args
*/
public static void main(String[] args) {
ILegalSuit suiter=new LegalSuiter();//构造一个被代理对象
DynamicProxyer dynaProxyer=new DynamicProxyer(suiter);//构造一个动态代理
ClassLoader loader=suiter.getClass().getClassLoader();
//动态构造一个具体的代理对象,在这绑定上了被代理对象
ILegalSuit dynaSuiter=(ILegalSuit) Proxy.newProxyInstance(loader, new Class[]{ILegalSuit.class}, dynaProxyer);
dynaSuiter.submit();
dynaSuiter.proof();
dynaSuiter.debate();
}
}
由以上例子不难得出,动态代理其实是同一个动态代理类来来代理N多个不同的代理类,为的就是对被代理者和代理者解耦,而静态代理这是只能在开发阶段就决定了被代理者。
1、远程代理
为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
2、虚拟代理
虚拟代理的基本思想其实和延迟初始化相似,如果需要创建一个资源消耗较大的复杂对象,先创建一个小的对象来表示,等到真正需要用的时候才会创建真实的对象。当用户请求一个大的对象时候,虚拟代理暂时充当真实对象的角色,待该真实对象真正被创建出来之后,虚拟代理就会将用户请求委托给真是对象。简而言之,使用一个代理对象暂时表示一个消耗巨大资源的真实对象,而真是对象仅仅在真正使用的时候才被创建。
虚拟代理模式(Virtual Proxy)是一种节省内存的技术,它建议创建那些占用大量内存或处理复杂的对象时,把创建这类对象推迟到使用它的时候。在特定的应用中,不同部分的功能由不同的对象组成,应用启动的时候,不会立即使用所有的对象。在这种情况下,虚拟代理模式建议推迟对象的创建直到应用程序需要它为止。对象被应用第一次引用时创建并且同一个实例可以被重用。这种方法优缺点并存。
2.1、虚拟代理的优点
这种方法的优点是,在应用程序启动时,由于不需要创建和装载所有的对象,因此加速了应用程序的启动。
2.1、虚拟代理的缺点
因为不能保证特定的应用程序对象被创建,在访问这个对象的任何地方,都需要检测确认它不是空(null)。也就是,这种检测的时间消耗是最大的缺点。
2.3、虚拟代理的使用逐一实现
应用虚拟代理模式,需要设计一个与真实对象具有相同接口的单独对象(指虚拟代理)。不同的客户对象可以在创建和使用真实对象地方用相应的虚拟对象来代替。虚拟对象把真实对象的引用作为它的实例变量维护。代理对象不要自动创建真实对象,当客户需要真实对象的服务时,调用虚拟代理对象上的方法,并且检测真实对象是否被创建。如果真实对象已经创建,代理把调用转发给真实对象,如果真实对象没有被创建:
-
代理对象创建真实对象
-
代理对象把这个对象分配给引用变量。
-
代理把调用转发给真实对象
按照这种安排,验证对象存在和转发方法调用这些细节对于客户是不可见的。客户对象就像和真实对象一样与代理对象进行交互。因此客户从检测真实对象是否为null中解脱出来,另外,由于创建代理对象在时间和处理复杂度上要少于创建真实对象。因此,在应用程序启动的时候,用代理对象代替真实对象初始化。
3、保护代理
当原始对象有不同的访问权限时,使用代理控制对原始对象的访问。可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
4、智能引用
在访问原始对象时执行一些代理自己附加的操作并对原始对象的引用计数。
未完待续...