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