代理模式
为实际要访问的对象创建一个代理,客户不再直接访问原始对象,而是通过代理对象,间接访问原始对象,这样就可以控制客户对原始对象对访问,或者在访问原始对象前增加预处理等。代理模式常用的场景包括:
-
功能增强,在不修改原始对象接口的前提下增加新的功能。例如原先有一个FileOperator类,负责各种文件操作(打开,关闭,删除等等), 如果现在需要对于每个操作加上日志,怎么实现呢? 最简单的方法当然时直接修改FileOperator类,对每个操作加上日志,但是这种的缺点是:
(1) 干扰FileOperator核心功能,FileOperator类添加了大量非核心代码,导致代码混乱,如果后续由大量额外功能,FileOperator会非常混乱,并且没法隔离不同的额外需求,例如有两个功能:(1)将log纪录到磁盘,(2)将log上传到服务器, 如果这两个功能只能2选1,那就非常麻烦了
(2) 如果FileObserver来自于第三方库,可能无法修改
(3) FileObserver来自于第三方,且可修改源码,这种情况如果将来需要合并新的代码,可能会出现大量到冲突
因此通常都不会直接修改FileOperator类,而是通过代理的方式实现,创建一个代理类FileOperatorProxy(静态代理),并封装FileOprator类。这种方式非常像装饰者模式,主要区别在于,装饰者模式会定义统一的接口,装饰者必须实现被装饰者的所有接口,而静态代理只需要定义需要的接口,其他不需要访问的接口可以省略。 -
远程代理
主要用于客户无法直接访问原始对象的情况,需要通过Proxy实现与远程对象的交互,例如Binder机制,客户端直接访问BinderProxy的接口,proxy通过Binder驱动与远端Binder对象进行交互。
静态代理
通过手动创建一个Proxy类,并且封装目标类,例如上述FileOperatorProxy。所有对FileOperator的访问都需要通过FileOpratorProxy预处理。静态代理最大的缺点在于对原始类对访问能力局限于Proxy类,如果想暴露原始类的所有接口,Proxy类就得定义原始类的所有接口的访问入口,如果接口很多,并且很多接口实际上是不需要预处理,则Proxy 类会写很多无用代码, 例如
public class FileOperator {
public void writeToFile() {
//write content to file
}
public void getOperateCount() {
//get operate count
}
}
实现一个Proxy 类,纪录log
public class FileOperatorProxy {
private FileOperator target;
public FileOperatorProxy(FileOperator target) {
this.target = target;
}
public void writeToFile() {
//print log
target.writeToFile();
}
public int getOperateCount() {
return target.getOperateCount();
}
}
显示,FileOperatorProxy类的getOperateCount这个方法就很累赘,但是为了暴露FileOperator的getOperateCount方法,Proxy 类必须实现大量这样的无用接口,非常繁琐。这就有必要用到动态代理了。
动态代理
于静态代理的区别在于,静态代理类必须预先创建,而动态代理类是运行时根据原始类的接口(注意,必须是interface, 否则会抛异常)动态创建Proxy, 通过InvocationHandler接口,所有对Proxy对象对访问都统一调用InvocationHandler的invoke接口,这样,所有的增强操作都可以在invoke中完成,不需要增强的方法,直接调用原始对象的方法,因此动态代理的关键就在于实现InvocationHandler的invoke对象,对特定方法进行处理,其他方法不做任何处理:
public interface IFileOperator {
void writeToFile();
int getOperateCount();
}
public class FileOperator implements IFileOperator {
@Override
public void writeToFile() {
//writeToFile
}
@Override
public int getOperateCount() {
//return operate count
}
}
public class MyInvocationHandler implements InvocationHandler {
FileOperator operator;
public MyInvocationHandler(FileOperator operator) {
this.operator = operator;
}
@Override
public void invoke(Object proxy, Method method, Object[] args) {
if (method.getName().equals("writeToFile")) {
//print log
operator.writeToFile();
} else {
method.invoke(operator, args);
}
}
public IFileOperator createProxy(IFileOperator operator) {
IFileOperator proxy = (IFileOperator)
Proxy.newInstance(ClassLoader.getSystemClassLoader(), new
Class[] {IFileOperator.class}, new MyInvocationHandler(operator);
return proxy;
}
可以看到,只需要对特定的方法做出来就行了,处于方法不做任何处理,就可以暴露原始对象的所有方法,避免了大量无用的重复方法, 这就是动态代理的优势。这里可能会有一个担心:每次创建代理的时候都需要动态创建一个Proxy Class,会不会有很多开销?实际上是不用担心的,Proxy.newInstance方法首先根据传入的interface列表创建Class,创建前会先检查对应的列表是否已经创建过Proxyclass,如果已创建,则复用,这样实际上每次调用Proxy.newInstance也只是创建了一个Proxy对象而已,开销跟静态代理是一样的
网友评论