设计模式 ~ 代理设计模式
代理设计模式是结构性设计模式的一种,你可以这么理解代理,当你需要在家撸代码后提交到 Gitlab 上,但是发现由于公司内网的原因你必须连上公司内网的VPN代理才能访问公司的代码库,所以说在日常生活中代理设计模式主要运用于你想访问某一个网站或者别的什么,但由于个别因素导致你没法正常访问,这时候就需要代理对象来协助你来访问。
定义:
代理模式也称委托模式,为其它对象提供代理用来控制对这个对象的访问,当无法或者不想直接访问一个对象或访问这个对象有困难的时候可以通过一个代理对象来间接的访问这个对象,为了保证客户端的透明性,委托对象(就是你需要访问的那个对象)
和代理对象需要实现相同的接口(参考自《Android源码设计模式解析与实战》)
。
代理模式的实现方式
我是个码农,我在家里写完需求后需要提交代码到公司的代码库中,但由于公司内网的原因我必须要挂上公司VPN代理才能提交代码,根据以上情形我定义了以下几个类:
ISubject:为保证客户端程序的透明性,无论代理对象还是委托对象都需要实现这个接口,在接口里定义需要代理的方法。
SubjectImpl:具体的委托类,也就是客户端需要通过代理访问的那个类对应着上文需求中的代码库
。
SubjectProxy:具体代理类,实现了 ISubject 接口,重写需要代理的方法,在其内部维护了委托对象的实例代理对象你可以理解为上文中的VPN客户端
。
Client:不解释,客户端类,苦逼的程序猿。
代理模式UML静态代理
静态代理可以看作是对委托对象的扩展,在委托对象的外围包装了一层,并且在代理对象中维护了委托对象的实例,码农在访问公司代码库的时候实际上是通过中间代理对象 SubjectProxy
中维护的“代码库对象SubjectImpl
”来间接实现访问的操作的,下面用代码形式来体现下具体的操作,并要求连接成功后给客户端一个连接成功的反馈。
/**
* 定义接口,委托对象和代理对象都需要实现这个接口
*/
public interface ISubject {
String commitCode(String code);
}
/**
* 代码库:我们的客户端需要具体访问的对象
*/
public class SubjectImpl implements ISubject {
@Override
public String commitCode(String code) {
return "代码提交成功,本次提交的代码为 : "+code;
}
}
/**
* 代理对象中维护了"委托对象"的实例,并通过"委托对象"达到间接访问的目的
*/
public class SubjectProxy implements ISubject {
private ISubject iSubject;
public SubjectProxy(ISubject iSubject) {
this.iSubject = iSubject;
}
@Override
public String commitCode(String code) {
return iSubject.commitCode(code);
}
}
/**
* 码农:
*/
public class Client {
public static void main(String[] args){
SubjectImpl subjectImpl = new SubjectImpl();
//假设我们没法直接访问 SubjectImpl ,这时候就需要 SubjectProxy 来协助间接访问
SubjectProxy subjectProxy = new SubjectProxy(subjectImpl);
String request = subjectProxy.commitCode("Hello World");
System.out.println(request);
}
}
可以看到通过代理模式可以非常方便的帮助客户端去访问“访问起来比较困难”的对象,如果仅仅只是非常简单的小需求静态代理倒是可以应付,但就以上的情形如果码农需要执行 clone 、 push 、 pull 的操作,那么我还需要修改接口并增加对应的方法,光是这个小 Demo 就需要大动干折了,其次每一个委托对象对应着一个代理接口,更何况是比较复杂的系统呢?这时候就需要动态代理出马了,我们接着来看什么是动态代理。
动态代理
动态代理与静态代理的区别在于动态代理可以帮助我们自动生成代理类,这也就避免了静态代理中的问题,动态代理主要借助于 JDK reflect
包下的 InvocationHandler
和 Proxy
,并且它的生成也是有固定规则的:
- 第一步,定义委托类接口
ISubject
,这与上文中静态代理的处理方式是一样的 - 第二步,定义委托类
RealSubject
,与静态代理代码一样 - 第三步,定义
MyInvocationHandler
继承InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
/**
* 委托类对象,在构造方法中传入
*/
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
*
* @param proxy 代理对象
* @param method 反射中的 Method 类,通过反射来找到委托类中的方法
* @param args 委托类中需要代理的方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
- 第四步,定义客户端对象并通过
Proxy
来生成代理类
public class Client {
public static void main(String[] args) {
ISubject subject = new RealSubject();
//初始化 MyInvocationHandler ,并传入委托类的实例
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(subject);
//通过 Proxy 生成代理类,Proxy.newProxyInstance返回的是自动生成的代理对象
ISubject subjectProxy = (ISubject) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
subject.getClass().getInterfaces(),myInvocationHandler);
//通过代理类调用相关方法
subjectProxy.commitCode("Hello World");
}
}
动态代理的代理类都是自动生成的,所以相比较静态代理而言并不需要维护一个代理类,如果委托类中需要新增被代理的方法只需要在接口中定义方法然后重写逻辑,少了一个维护代理类的过程,也就避免了程序的臃肿。