从上节的学习想必大家都知道动态代理怎么写了,现在我们拓展一些,动态代理和装饰器模式,想必大家都很困惑。本小节主要介绍动态代理和装饰器模式的不同以及如何相互搭配。
java 系列: 动态代理(上)-一个简单的动态代理
java 系列: 动态代理(中)-与装饰器模式的搭配与使用
java 系列: 动态代理(下)-与Android相关的Hook技术
1.动态代理和装饰器模式
1.装饰器模式的介绍:
装饰器模式能够实现保持原有的接口,并为新的接口动态的添加功能。装饰器实现了java编程上的一个准则,多组合,少继承。
装饰器的一个典型的例子是:java中的IO流的实现,大家在转过头来看javaIO流的思维导图,想必会恍然大悟了。
例图:(ps:这个图会和我们下面将要讲的装饰器模式+动态代理的UML图一致,不再重复显示)
2.两者区别
两者都可以在保持主业务流程下,对对象进行添加方法,增强这个类的使用。
但两者有着一些区别:
装饰器模式继承了接口,添加了新类,而代理没有对原代码有任何操作。
装饰器中的继承和创建新类,会造成出现大量的包装类,对于代码阅读是有困难的。
2.动态代理的装饰器模式搭配
为什么要这样搭配呢,想必大家在实践过程中使用代理都会遇到的问题–动态代理的扩展性。
动态代理是没有办法在源代理类上直接进行拓展的(否则的话全部的接口都会实现拓展的对象),但对于我们来说,并不需要,只是对某些委托类进行操作,这就不太这么方便操作了。所以这时候使用装饰器模式比较好的搭配使用,添加自定义的扩展。
相对于简单的动态代理,这时候要增加几个新的方法:
提供动态代理实例的静态工厂类(ProxyFactory)和抽象装饰器(Decorator)
- 首次我们看抽象主题接口
public interface AbstractSubject {
void request();
}
- 真实主题类(委托类/被代理的对象)
public class RealSubject implements AbstractSubject {
public void request() {
System.out.println("真实的方法-----RealSubject's request() ...");
}
}
- 抽象装饰器类(一定要以自己的父类、父接口为一个属性)
public class Decorator implements AbstractSubject {
protected AbstractSubject subject = null;
public Decorator(AbstractSubject subject) {
this.subject = subject;
}
@Override
public void request() {
}
}
- 具体装饰器1(根据实际情况,我这里只写三个)
public class ConcreteDecorator01 extends Decorator {
public ConcreteDecorator01(AbstractSubject subject) {
super(subject); //调用父类装饰类的构造器
}
/**
* 覆盖继承树上的接口中的request()方法,用于装饰原对象
*/
public void request() {
System.out.println("第一层装饰 ... 装饰在原主题之前");
super.subject.request();
}
}
- 具体装饰器2(根据实际情况,我这里只写三个)
public class ConcreteDecorator02 extends Decorator {
public ConcreteDecorator02(AbstractSubject subject) {
super(subject); //调用父类装饰类的构造器
}
/**
* 覆盖继承树上的接口中的request()方法,用于装饰原对象
*/
public void request() {
super.subject.request();
System.out.println("第二层装饰 ... 装饰在原主题之后");
}
}
- 具体装饰器3(根据实际情况,我这里只写三个)
public class ConcreteDecorator03 extends Decorator {
public ConcreteDecorator03(AbstractSubject subject) {
super(subject); //调用父类装饰类的构造器
}
/**
* 覆盖继承树上的接口中的request()方法,用于装饰原对象
*/
public void request() {
super.subject.request();
System.out.println("第三层装饰 ... 装饰在原主题之后");
}
}
- 提供动态代理实例的静态工厂类(管理代理实现)
public class ProxyFactory {
/**
* @param realSubject :指定需要代理的真实主题类的实例
* @return proxy :代理的实例
*/
public static AbstractSubject getProxy(AbstractSubject realSubject) {
// 获得被代理类的类加载器,使得JVM能够加载并找到被代理类的内部结构,以及已实现的interface
ClassLoader loader = realSubject.getClass().getClassLoader();
// 获得被代理类已实现的所有接口interface,使得动态代理类的实例
Class<?>[] interfaces = realSubject.getClass().getInterfaces();
// 用被代理类的实例创建动态代理类的实例,用于真正调用处理程序
InvocationHandler handler = new DynamicProxy(realSubject);
/*
* 使用java.lang.reflect.Proxy类中的静态方法newProxyInstance()获得代理的实例
*
* loader : 被代理类的类加载器 interfaces :被代理类已实现的所有接口,而这些是动态代理类要实现的接口列表 handler
* : 用被代理类的实例创建动态代理类的实例,用于真正调用处理程序
*
* return :返回实现了被代理类所实现的所有接口的Object对象,即动态代理,需要强制转型
*/
AbstractSubject proxy = (AbstractSubject) Proxy.newProxyInstance(
loader, interfaces, handler);
return proxy;
}
}
- 代理类
public class DynamicProxy implements InvocationHandler {
// 被代理类的实例
Object obj = null;
// 将被代理者的实例传进动态代理类的构造函数中
public DynamicProxy(Object obj) {
this.obj = obj;
}
/**
* 覆盖InvocationHandler接口中的invoke()方法
*
* 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
* 控制被代理对象的行为,下面的before、after就是我们可以进行特殊 代码切入的扩展点了。
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*
* before :doSomething();
*/
System.out.println("动态代理为真实主题添加一个方法 ...");
Object result = method.invoke(this.obj, args);
/*
* after : doSomething();
*/
return result;
}
}
- client
public class Client {
public static void main(String[] args) {
// 被代理类的实例
AbstractSubject realSubject = new RealSubject();
// 通过静态工厂获取动态代理的实例
AbstractSubject proxy = ProxyFactory.getProxy(realSubject);
// 装饰之前打印出该代理实例的名称
System.out.println("装饰前:" + proxy.getClass().getName());
// 装饰前使用代理实例进行原始操作
//proxy.request();
System.out.println("\n第一次装饰之后的效果如下:");
proxy = new ConcreteDecorator01(proxy);
System.out.println("\n名称:" + proxy.getClass().getName());
//proxy.request();
System.out.println("\n第二次装饰之后的效果如下:");
proxy = new ConcreteDecorator02(proxy);
System.out.println("\n名称:" + proxy.getClass().getName());
// proxy.request();
System.out.println("\n第三次装饰之后的效果如下:");
proxy = new ConcreteDecorator03(proxy);
System.out.println("\n名称:" + proxy.getClass().getName());
proxy.request();
}
}
总结
通过上面的代码描述可以在proxy这个类已经是处理好之后的情况,在后面的大段代码都是标准的装饰器代码。
运行之后:
看好我画圈的地方,下面将AbstractSubject proxy = ProxyFactory.getProxy(realSubject);
方法去除,变成一个完整的装饰器模式,看看效果:
可以看到,结果完全相同,只有最初代理的对象不同,一个是经过代理产生的,一个是源接口。
对于这样的好处毋庸置疑:代理可以放在其他模块中,对于自定义的功能添加使用装饰器自由组合,对于统一的操作放在代理对象中,这样解耦的形式比较好。
3.结束语
其实设计模式之前的搭配有很多,只学单独的设计模式的概念并不是很大,很多注明的SDK都是很多设计模式相互搭配而来,请大家多多思考,下节讲动态代理的实际操作–Android中统一处理防双击/抖动。谢谢