漫谈设计模式(一):代理(Proxy)模式与适配器(Adapter)模式对比
1.前言
为什么要将代理模式与适配器模式放在一起来说呢?因为它们有许多的共同点,当然也有一些不同的地方。首先两者都是属于结构型模式。结构型模型是这样定义的:
结构型模式涉及到如何组合类和类以获得更大的结构,结构型类模式采用继承机制来组合接口或实现。
1)代理模式与适配器模式都分别有继承、接口方式实现的子分类模式。基于接口实现的代理模式称为静态代理模式、JDK(动态)代理模式,基于继承实现的代理模式称为Cglib(动态)代理模式。基于接口(同时含类继承)实现的适配器模式称为类适配器模式,(只)基于继承(使用委托)实现的适配器模式称为类适配器模式。
2)代理模式是为其他类提供一种代理以控制对这个类的访问。我们不直接去接触目标类,而是直接操作代理类,代理类再去操作目标类。因为不直接接触目标类,因此我们可以在代理类的同名方法中添加或删除功能模块,而不用去修改目标类的原方法。
而适配器模式则主要是协调现实与需求的差异,减少对已有代码的改动,适配不同的接口、类类型。项目实施中可能会出现这样的情况:当前已完成的项目的某一个包内的各个类实现了一些特定的接口,而客户提出了新的需求,要求实现他所指定的那些接口(抛弃原有的方法或接口),但其业务细节却是相同、完全一样的。此时,我们可能并不想复制粘贴原代码到新的方法中去,这就需要将一个类的接口转换成新需求的另一个接口。
2.代理模式
1)静态代理
静态代理,必须提供一(多)个公用的接口。目标类和代理类都要实现这个接口,外界调用代理类的(接口)方法,代理类的方法再去调用目标类(接口)的方法。
public class StaticProxy { /** * 定义一个接口 * * @author Administrator * */ public static interface Human { void behave(); } /** * 定义一个实现Human接口的具体类Student * * @author Administrator * */ public static class Student implements Human { String name; public Student() { } public Student(String name) { super(); this.name = name; } @Override public void behave() { System.out.println(name + "上学去了"); } } /** * 定义静态代理类 * * @author Administrator * */ //代理类和目标类都实现了Human方法 public static class StudentProxy implements Human{ private Human target; // 目标对象 /** * 构造方法 * * @param target 目标对象 */ protected StudentProxy(Human target) { this.target = target; } @Override public void behave() { System.out.println("+++++++目标方法执行前打印加号+++++++"); target.behave(); System.out.println("*******目标方法执行后打印星号*******"); } } public static void main(String[] args) { Human studentProxy=new StudentProxy(new Student("何峰")); studentProxy.behave(); } }
定义一个公用的接口
public static interface Human { void behave(); }
实现接口的目标类
public static class Student implements Human { String name; public Student() { } public Student(String name) { super(); this.name = name; } @Override public void behave() { System.out.println(name + "上学去了"); } }
实现接口的代理类
public static class StudentProxy implements Human{ private Human target; // 目标类 protected StudentProxy(Human target) { this.target = target; } @Override public void behave() { System.out.println("+++++++目标方法执行前打印加号+++++++"); target.behave(); System.out.println("*******目标方法执行后打印星号*******"); } }
主方法测试
public static void main(String[] args) { Human studentProxy=new StudentProxy(new Student("何峰")); studentProxy.behave(); }
控制台结果输出
2)JDK动态代理
JDK动态代理,主要是利用JDK提供的API动态生成代理类实例。
我们不需要去写一个具体代理类,只需要一个代理工厂即可,JDK的系统API将在运行时动态生成代理类的字节码,并此字节码将加载到JVM中,在JVM中直接构建代理类(不需要将class文件读取到内存中)。
要用到的JDK的API
(1)java.lang.reflect.Proxy工具类
我们主要会用到的静态方法 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
这个静态方法将能够动态构建一个实现了和目标类相同的接口的代理类(具体实现的接口个数,此方法中的参数interfaces有关),此方法的三个参数分别是目标类的类加载器loader、目标类实现的接口数组interfaces、 InvocationHandler类handler。
(2)java.lang.reflect.InvocationHandler接口
此接口只有一个抽象方法 public Object invoke(Object proxy, Method method, Object[] args)throws Throwable
这个抽象方法的三个参数分别表示代理类proxy, 目标类方法method,目标类方法的参数args。
这是一个回调方法,当我们调用代理类所实现的接口方法(和目标类相同的接口相同)时,java虚拟机会调用此方法。所以重写此方法非常重要,可以在此方法中添加我们所需要的增强效果。
实现细节
为了直观简便地测试效果,特别将所需类都作为内部类放在JDKProxy中
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKProxy { /** * 定义一个接口 * * @author Administrator * */ public static interface Human { void behave(); } /** * 定义一个实现Human接口的具体类Student * * @author Administrator * */ public static class Student implements Human { String name; public Student() { } public Student(String name) { super(); this.name = name; } @Override public void behave() { System.out.println(name + "上学去了"); } } /** * 定义动态代理抽象工厂类 * * @author Administrator * */ // 继承InvocationHandler,但不实现其接口方法,接口方法的具体实现交给抽象工厂类的子类去完成 public static abstract class ProxyFactory implements InvocationHandler { // 访问权限设置为保护级,其子类能继承到此成员变量 protected Object target; // 目标对象 /** * 构造方法 * * @param target 目标对象 */ // 只有这一个单参数构造方法,访问权限同时也设为保护级,则子类必须显式调用此构造方法 protected ProxyFactory(Object target) { super(); this.target = target; } /** * 动态生成代理对象 * * @return 代理对象 */ // 方法设为final,防止子类重写此方法 public final Object getProxyInstance() { /* * Proxy.newProxyInstance方法的三个参数依次是目标对象的类加载器loader、目标对象实现的接口数组interfaces、 * InvocationHandler对象handler。 * InvocationHandler是个接口,而当前抽象类implements此接口,此处的抽象类并没有具体实现此接口,具体实现将交给子类 * */ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } } /** * 上小学的代理工厂类 * @author Administrator * */ public static class PrimaryProxyFactory extends ProxyFactory { /** * 动态生成代理对象 * * @return 代理对象 */ private PrimaryProxyFactory(Object target) { super(target); } /** * 实现InvocationHandler接口,增强目标对象实现接口的方法 */ @Override public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("小学学校离家5km。"); //目标对象方法执行前的修饰 Object returnVal = method.invoke(this.target, args); //调用目标对象的相应方法 System.out.println("早上去上学费时30分钟。");//目标对象方法执行后的修饰 return returnVal; } } /** * 上高中的代理工厂类 * @author Administrator * */ public static class SeniorProxyFactory extends ProxyFactory { /** * 动态生成代理对象 * * @return 代理对象 */ protected SeniorProxyFactory(Object target) { super(target); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("高中学校离家50km。"); Object returnVal = method.invoke(this.target, args); System.out.println("去一次学校就要费时两个小时。"); return returnVal; } } public static void main(String[] args) { Student stu = new Student("李高峰"); //创建代理工厂对象 ProxyFactory primaryProxyFactory = new PrimaryProxyFactory(stu); //代理对象向上转型为Human接口 Human proxyPrimaryHuman = (Human) primaryProxyFactory.getProxyInstance(); //代理对象执行接口方法 proxyPrimaryHuman.behave(); System.out.println(""); ProxyFactory seniorProxyFactory = new SeniorProxyFactory(stu); Human proxySeniorHuman = (Human) seniorProxyFactory.getProxyInstance(); proxySeniorHuman.behave(); } }
先定义一个接口Human
public static interface Human { void behave(); }
定义一个实现Human接口的目标类Student
public static class Student implements Human { String name; public Student() { } public Student(String name) { super(); this.name = name; } @Override public void behave() { System.out.println(name + "上学去了"); } }
定义动态代理的抽象工厂类
// 继承InvocationHandler,但不实现其接口方法,接口方法的具体实现交给抽象工厂类的子类去完成 public static abstract class ProxyFactory implements InvocationHandler { // 访问权限设置为保护级,其子类能继承到此成员变量 protected Object target; // 目标对象 /** * 构造方法 * * @param target 目标对象 */ // 只有这一个单参数构造方法,访问权限同时也设为保护级,则子类必须显式调用此构造方法 protected ProxyFactory(Object target) { super(); this.target = target; } /** * 动态生成代理对象 * * @return 代理对象 */ // 方法设为final,防止子类重写此方法 public final Object getProxyInstance() { /* * Proxy.newProxyInstance方法的三个参数依次是目标对象的类加载器loader、 * 目标对象实现的接口数组interfaces、InvocationHandler对象handler。 * InvocationHandler是个接口,而当前抽象类implements此接口,此处的抽象类并没有具体实现此接口,具体实现将交给子类 */ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
定义抽象工厂类的子类(实现类)
/** * 上小学的代理工厂类 * @author Administrator */ public static class PrimaryProxyFactory extends ProxyFactory { public PrimaryProxyFactory(Object target) { super(target); } /** * 实现InvocationHandler接口,增强目标类实现接口的方法 */ @Override public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("小学学校离家5km。"); //目标类方法执行前的修饰 Object returnVal = method.invoke(this.target, args); //调用目标类的相应方法 System.out.println("早上去上学费时30分钟。");//目标类方法执行后的修饰 return returnVal; } } /** * 上高中的代理工厂类 * @author Administrator * */ public static class SeniorProxyFactory extends ProxyFactory { public SeniorProxyFactory(Object target) { super(target); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("高中学校离家50km。"); Object returnVal = method.invoke(this.target, args); System.out.println("去一次学校就要费时两个小时。"); return returnVal; } }
主方法内进行效果测试
public static void main(String[] args) { Student stu = new Student("李高峰"); //创建代理工厂类 ProxyFactory primaryProxyFactory = new PrimaryProxyFactory(stu); //代理类向上转型为Human接口 Human proxyPrimaryHuman = (Human) primaryProxyFactory.getProxyInstance(); //代理类执行接口方法 proxyPrimaryHuman.behave(); System.out.println(""); ProxyFactory seniorProxyFactory = new SeniorProxyFactory(stu); Human proxySeniorHuman = (Human) seniorProxyFactory.getProxyInstance(); proxySeniorHuman.behave(); }
控制台效果输出
3)Cglib代理
静态代理和JDK动态代理都必须要求目标类实现了一(多)个接口,而对未实现任何接口的目标类无法进代理处理。Cglig动态代理就是为了解决这一问题而产生的代理技术。它是在运行时,利用asm字节码框架,动态生成目标类的子类的字节码,并加载到java虚拟机中,运行期动态地创建目标类的子类类。Cglib代理需要外部框架支持,而spring-core-xxx.jar包中就含有Cglib所需的class文件。
使用Cglib代理有以下两点要求:
(1)目标类不能被定义为final。--------------------------- 定义的final的类不能被继承扩展,当然就不可能创建出它的(代理类)子类
(2)目标类的方法不能为final或static修饰------------- final方法不能被重写,(子类)代理类也就不能对此方法进行扩展。静态方法与类本身相关联,不依赖于实例类存在,无法实现多态。
Cglib代理的关键点:
(1)方法拦截器接口 org.springframework.cglib.proxy.MethodInterceptor
此接口中只有一个方法 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
此方法为回调方法,在运行期间此方法是将动态地生成代理类(子类)的代理方法(重写目标类的方法)。
此方法的四个参数分别是代理类类proxy、目标类方法类method、目标类方法的参数args、代理类的方法(spring框架中的数据类型,不同于JDK中的Method)类methodProxy。
(2)代理方法增强工具类 org.springframework.cglib.proxy.Enhancer
其方法一 | void setSuperclass(Class supperClass) | 设置目标类(即代理类的父类)的Class模板类 |
其方法二 | void setCallback(MethodInterceptor methodInterceptor) | 设置回调方法,其intercept(......)方法定义了代理类(即目标类的子类)的方法重写规范 |
其方法三 | Object create() | 创建并返回代理类类 |
实现过程
import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import proxy3.ProxyFactory; public class CglibProxy { public static class Student { private String name = "xxx"; public void setName(String name) { this.name = name; } public Student() { } public void behave() { System.out.println(name + "上学去了"); } } /** * 定义动态代理工厂类 */ // 继承MethodInterceptor public static class ProxyFactory implements MethodInterceptor { private static final ProxyFactory proxyFactory; static { proxyFactory = new ProxyFactory(); } /** * 返回代理工厂单例 * @return */ public static ProxyFactory getThis() { return proxyFactory; } /* * 构造方法私有化 */ private ProxyFactory() { } /** * 创建目标类的代理对象 * * @param targetClass 目标类的元类 * @return 代理对象 */ @SuppressWarnings("unchecked") public <T> T createProxyInstance(Class<T> targetClass) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetClass); enhancer.setCallback(this); return (T) enhancer.create(); //代理对象向上转型为父类类型,即目标类类型 //return (T) Enhancer.create(targetClass, this); 此方法可一步到位 } /* *拦截器接口MethodInterceptor中的方法 */ @Override public Object intercept(Object proxyObj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object returnVal = null; if (method.getName().startsWith("be")) { //只对特定方法拦截 System.out.println("上学前记得带作业"); returnVal = methodProxy.invokeSuper(proxyObj, args); //调用目标方法 System.out.println("小心路上的汽车"); }else { returnVal = methodProxy.invokeSuper(proxyObj, args); } return returnVal; } } public static void main(String[] args) { ProxyFactory proxyFactory=ProxyFactory.getThis(); Student stuProxy= proxyFactory.createProxyInstance(Student.class); stuProxy.setName("小王"); stuProxy.behave(); } }
未实现任何接口的目标类
public static class Student { private String name = "xxx"; public void setName(String name) { this.name = name; } public Student() { } public void behave() { System.out.println(name + "上学去了"); } }
实现MethodInterceptor接口的代理工厂类
public static class ProxyFactory implements MethodInterceptor { private static final ProxyFactory proxyFactory; static { proxyFactory = new ProxyFactory(); } /** * 返回代理工厂单例 * @return */ public static ProxyFactory getThis() { return proxyFactory; } /* * 构造方法私有化 */ private ProxyFactory() { } /** * 创建目标类的代理对象 * * @param targetClass 目标类的元类 * @return 代理对象 */ @SuppressWarnings("unchecked") public <T> T createProxyInstance(Class<T> targetClass) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetClass); enhancer.setCallback(this); return (T) enhancer.create(); //代理对象向上转型为父类类型,即目标类类型 //return (T) Enhancer.create(targetClass, this); 此方法可一步到位 } /* *拦截器接口MethodInterceptor中的方法 */ @Override public Object intercept(Object proxyObj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object returnVal = null; if (method.getName().startsWith("be")) { //只对特定方法拦截 System.out.println("上学前记得带作业"); returnVal = methodProxy.invokeSuper(proxyObj, args); //调用目标方法 System.out.println("小心路上的汽车"); }else { returnVal = methodProxy.invokeSuper(proxyObj, args); } return returnVal; } }
主方法内进行测试
public static void main(String[] args) { ProxyFactory proxyFactory=ProxyFactory.getThis(); Student stuProxy= proxyFactory.createProxyInstance(Student.class); stuProxy.setName("小王"); stuProxy.behave(); }
在控制台 测试结果输出
3.适配器模式
1)类适配器模式
基本思路
类适配器模式主要应用于新需求是一个新的接口。为了适应新的需求,我们必须先实现此接口,但我们又要想复用原有的代码。此时要满足这一点,可通过继承原有的类,来复用父类的方法。综合以上的两个关键点,我们应该定义一个新的类,使其继承原有的类,同时去实现业务所需的新目标接口。
实现过程
public class ClassAdapter { /** * 目标接口 * @author Administrator * */ public static interface Animal { void act(); } /* * (现有的类)被适配者 */ public static class Student { private String name; public Student() { } public Student(String name) { super(); this.name = name; } public void behave() { System.out.println(name + "上学去了"); } } /** * 适配者(器) * */ public static class Actor extends Student implements Animal{ public Actor() { super(); } public Actor(String name) { super(name); } @Override public void act() { /** * 调用父类Student的behave()方法,使当前act()方法产生与behave()完全相同的效果输出, * 但在外界来看,是实现了新的行为方法,外界只会调用act方法,而不知其内部具体实现 * 实际上还是在执行behave()方法 */ super.behave(); } } public static void main(String[] args) { Animal actor= new Actor("张天一"); actor.act(); } }
原有的学生类Student具有behave行为
public static class Student { private String name; public Student() { } public Student(String name) { super(); this.name = name; } public void behave() { System.out.println(name + "上学去了"); } }
新的目标需求是实现Animal接口,使其具有Animal的能力,但行为细节不用改变
public static interface Animal { void act(); }
定义一个新类(适配者),让其适配新的需求,同时复用已有的代码
public static class Actor extends Student implements Animal{ public Actor() { super(); } public Actor(String name) { super(name); } @Override public void act() { /** * 调用父类Student的behave()方法,使当前act()方法产生与behave()完全相同的效果输出, * 但在外界来看,是实现了新的行为方法,外界只会调用act方法,而不知其内部具体实现 * 实际上还是在执行behave()方法 */ super.behave(); } }
主方法测试
public static void main(String[] args) { Animal actor= new Actor("张天一"); actor.act(); }
控制台输出
2)对象适配器模式
设计思路
对象适配器模式主要应用于新的需求的一个(抽象)基类。在这里我们不能使用类适配器模式的所用到的继承父类、实现接口的方式去满足目标需求,因为java中不允许多根继承,只能单根继承,我们也就无法在继承(目标需求)抽象类的同时再去继承原有的类(复用原代码)。此时定义一个新类(适配者),将一个原有的类类(被适配者)作为成员变量放在这个类中,此类还要继承目标抽象类,并实现其中的抽象方法。
实现过程
public class ObjectAdapter { /** * 目标抽象类 * @author Administrator * */ public static abstract class Animal { public abstract void act(); } /* * (现有的类)被适配者 */ public static class Student { private String name; public Student() { } public Student(String name) { super(); this.name = name; } public void behave() { System.out.println(name + "上学去了"); } } /** * 适配者(器) * */ public static class Actor extends Animal{ private Student student; //student属性,让构造方法传的Student对象参数让其初始化 public Actor(Student student) { super(); this.student = student; } @Override public void act() { student.behave(); } } public static void main(String[] args) { Animal actor=new Actor(new Student("张一峰")); actor.act(); } }
现有学生类Student (被适配者)
public static class Student { private String name; public Student() { } public Student(String name) { super(); this.name = name; } public void behave() { System.out.println(name + "上学去了"); } }
目标需求的抽象类Animal
public static abstract class Animal { public abstract void act(); }
定义新类(适配者),继承(需求)抽象类,Student(被适配者)作为它的成员变量
public static class Actor extends Animal{ private Student student; //student属性,让构造方法传的Student类参数让其初始化 public Actor(Student student) { super(); this.student = student; } @Override public void act() { student.behave(); } }
主方法测试
public static void main(String[] args) { Animal actor=new Actor(new Student("张一峰")); actor.act(); }
控制台输出
4.总结
(1)代理模式,按照是代理类否由java虚拟机动态生成的,可将代理模式分为静态代理和动态代理。
代理模式,为其他类提供一种代理以控制对这个类的访问 ,主要目的就是实现对目标类的方法增强。
静态代理,需要定义接口,代理类和目标类都实现此接口,然后代理类调用接口的同名方法来调用目标类的方法。需要注意的是:此代理类需要我们手动去接口中的每个方法去写实现,并在保存编译后classpath路径中将存在这个代理类的class文件。
然而大多数情况下, 对目标类方法的增强控制代码是相同或类似的,如果接口中的方法较多或接口较多,一 一为之编写代理类的接口实现方法,会增加工作量,造成代码冗余。而动态代理,则不需要一 一编写代理类对接口的实现方法,不需要编写具体的代理类,那么classpath中就不存在代理类的class文件,但需要定义统一的接口方法实现规范,即编写一个生成代理类的代理工厂类。而在动态代理中又可分为JDK(接口)代理、Cglib代理。
JDK代理需提供接口,动态生成的代理类实现此接口,实质上是根据代理工厂类的接口实现规范(如上面的PrimaryProxyFactory的"invoke(Object proxy, Method method, Object[] args)"方法)来实现 与目标类相同方法签名的接口方法以完成方法代理;而Cglib代理不需要提供接口,动态生成的代理类会去继承目标类,实质上也是根据代理工厂类的方法重写规范(如上面的ProxyFactory类的"intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)"方法)来重写父类的方法以实现方法代理。
(2)适配器模式,按照需求是就接口或(抽象)类,可分为类适配器模式和对象适配器模式。
适配器模式,将一个类的接口转换成客户希望的另外一个接口,它填补了“现有程序” 和“所需程序”之间的差异,增强代码的兼容复用能力,以适应不同的目标需求。
类适配器模式需要新定义一个适配器类,它实现了需求对应的接口,并继承现有的业务类,通过在接口方法中显式地调用父类的方法的方式,达到适应新需求同时又复用现有方法代码的目的。
对象适配器模式同样需要定义一个适配器类,它继承需求对应的(抽象)类,将现有的业务类作为适配器的一个成员变量,并用构造方法传入业务类实例对象的引用,以初始化此成员变量;在实现父类的抽象方法中使用委托机制(某个方法将实际处理工作交给其他对象的方法去完成),让成员变量(现有业务类对象)调用自己的方法。
(3)代理模式和适配器模式的比较
代理模式和对象适配器模式都需要持有一个对象的引用(作为成员变量),且此成员变量通过构造方法来初始化,使用委托机制完成想要的全部或部分的业务处理。
静态代理、JDK动态代理与类适配器模式都基于实现接口方法,而Cglib动态代理、对象适配器模式都基于继承(抽象)类。
从广义的角度上来看(参考C++中的多继承),实现接口方法、继承父类都属于广义上的“继承”,达成多态的效果。"继承"(包含实现接口)是设计模式中常用的策略,"继承"的目的就是代码重用和多态。无论是代理模式还是适配器模式,都是对满足新需求而应运而生的设计模式。不同之处在于,代理模式中,新的需求是具体业务有了变化,但接口或方法名没有变化;适配器模式中,新需求是接口或方法名变化了,但具体业务没有变化。为了充分利用变化中的不变因素,我们常使用广义上的“继承”机制来实现,所以代理模式和适配器模式都用到了大量的“继承”机制。
参考:Java的三种代理模式(岑宇);图解设计模式(结城浩)