与接口相关的设计模式(2):代理模式、标识类型模式及常量接口模式
2016-05-23 22:01
447 查看
在上文 与接口相关的设计模式(1) 中,详细介绍了定制服务模式和适配器模式,下面我们来看第三种与接口相关的模式:代理模式。
分类:
远程代理(Remote Proxy)—为不同地理的对象提供局域网代表对象。(类似于客户端和服务器端)
虚拟代理(Virtual Proxy)—根据需要将资源消耗很大的对象进行延迟,真正需要的时候才进行创建。(网页中图片的加载,先用一张虚拟的图片进行显示,等图片加载完成后再进行显示)
保护代理(Protect Proxy)—控制用户的访问权限。(发帖功能)
智能引用代理(Smart Reference Proxy)—提供对目标对象一些额外的服务。(火车站代售处为火车站代理)
以下以智能引用代理为例,介绍代理的两种实现方式:静态代理和动态代理。
静态代理:代理和被代理对象在代理之前是确定的,它们都实现了相同的接口或者继承相同的抽象类。
下面为简单示例,演示一个汽车对象被代理实现计时:
如果没有代理,汽车行驶的计时功能要在汽车本身行驶时实现:
对于这种情景,我们可以创建一个代理来专门实现计时功能,如下:
此时在Car类中的move方法可以只行驶,将计时功能交给代理去实现,Car类中的move方法代码如下:
现在我们要访问汽车时不用再直接实例化Car类,而是通过访问它的代理:CarProxy1实例,如下:
以上是通过继承的方式实现,还可以通过组合的方式实现,即不通过继承Car类,而是通过包装Car类来调用Car实例的行驶功能。代码如下:
通过组合方式创建的代理类,在实例化代理前,需要先实例化Car对象,代码如下:
与适配器模式中的类的适配器模式、对象的适配器模式相似,代理模式中组合方式比继承方式更灵活,推荐使用组合方式。比如再对Car实现日志记录等功能,使用继承方式则要再新建一个代理继承CarProxy1,之后再添加功能就要无限的向下继承,难于维护。
动态代理
上例中对汽车创建了一个时间记录代理,那么如果我们要对火车、飞机都记录时间呢?就需要分别对火车、飞机创建代理类。我们需要一个方法来把记录时间这个代理抽离出来,这样当需要对不同的交通工具实现记录时间功能的时候,直接运用这个抽离出的代理,这样可大大减少代码的重用率。这就引出了动态代理。
动态代理就是动态产生代理,实现对不同类,不同方法的代理。下面来看JDK动态代理。以下为JDK动态代理的实现机制:
如图所示,JDK的动态代理其实就是在代理类ProxySubject与被代理类RealSubject之间加入了一个ProxyHandler类,这个ProxyHandler类实现了InvocationHandler接口,它充当事务处理器,比如类似于上面给汽车计时的日志处理,时间处理等事务都是在这个ProxyHandler类中完成的。
InvocationHandler接口源码如下:
其中第一个参数proxy代表代理对象;第二个参数method代表被代理的方法;第三个参数代表该方法的参数数组。
JDK动态代理实现步骤:
创建一个实现InvocationHandler的类,必须实现invoke方法
创建被代理的类和接口
调用Proxy的静态方法,创建一个代理类
通过代理调用方法
1,创建一个实现InvocationHandler的类,必须实现invoke方法:
步骤 2,3,4,以下为Test类:
运行结果:
CGLIB动态代理:
CGLIB代理实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。主要使用于改造遗留系统,这些系统一般不会继承特地的接口。
cglib动态代理实现需要导入相关包,此处导入的为cglib-nodep-2.1_3.jar,然后需要创建事务处理器类,并实现MethodInterceptor接口,事务处理器类CglibProxy源码如下:
有一个火车类,并没有实现接口:
测试类:
测试结果:
总结: JDK动态代理和CGLIB动态代理的使用都很简单,如果能理解内部的具体实现流程才能更深刻的理解动态代理,关于JDK动态代理的模拟实现会在之后文章补充。
比如定义一个Food接口,其中不包含任何方法:
鱼肉:
进食方法:
进食:
所谓标识类型模式就是借助Java编译器来对传给eat()方法的food参数进行语义上的约束。Food接口被称为标识类型接口,这种接口没有任何方法,仅代表一种抽象类型。在JDK中,有如下两个典型的表示类型接口。
java.io.Serializable接口:实现该接口的类的实例可以被序列化
java.io.Remote接口:实现该接口的类的实例可以作为远程对象
以下Circle类需要访问以上MATH_PI常量,一种方式是采用直接访问方式,如下:
在JDK1.5中引入了”import static“语句,它允许类A直接访问另一个接口B或类B中的静态常量,而不必指定接口B或类B的名字,而且类A无须实现接口B或者继承类B。如下:
import static 语句既可以简化编程,又能防止Circle类继承并公开MyConstants中的静态常量。
结合上我的上一篇:与接口相关的设计模式(1):定制服务模式和适配器模式详解,一共记录了与接口相关的5种设计模式,分别是定制服务模式、适配器模式、代理模式、标识类型模式以及常量接口模式。接口是构建松耦合的软件系统的重要法宝。接口的优势在于一个类可以实现多个接口,接口获得这一优势是以不允许为任何方法提供实现作为代价的(暂不考虑JAVA8的Default方法)。
我们可以把接口作为系统中最高层次的抽象类型。站在外界使用者(另一个系统)的角度,接口向使用者承诺系统能提供哪些服务;站在系统本身的角度,接口指定系统必须实现哪些服务。系统之间通过接口进行交互,这可以提高系统之间的松耦合。
至于抽象类呢,它用来定制系统中的扩展点。可以把抽象类看作介于”抽象“和”实现“之间的半成品。抽象类力所能及的完成了部分实现,但还有一些功能有待于它的子类去实现。
代理模式
定义:为对象提供一种代理,以控制对这个对象的访问。分类:
远程代理(Remote Proxy)—为不同地理的对象提供局域网代表对象。(类似于客户端和服务器端)
虚拟代理(Virtual Proxy)—根据需要将资源消耗很大的对象进行延迟,真正需要的时候才进行创建。(网页中图片的加载,先用一张虚拟的图片进行显示,等图片加载完成后再进行显示)
保护代理(Protect Proxy)—控制用户的访问权限。(发帖功能)
智能引用代理(Smart Reference Proxy)—提供对目标对象一些额外的服务。(火车站代售处为火车站代理)
以下以智能引用代理为例,介绍代理的两种实现方式:静态代理和动态代理。
静态代理:代理和被代理对象在代理之前是确定的,它们都实现了相同的接口或者继承相同的抽象类。
下面为简单示例,演示一个汽车对象被代理实现计时:
package com.proxy; public interface Moveable {//汽车的行驶功能 void move(); }
如果没有代理,汽车行驶的计时功能要在汽车本身行驶时实现:
package com.proxy; import java.util.Random; public class Car implements Moveable{ @Override public void move() { long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶"); try{ Thread.sleep(new Random().nextInt(1000)); System.out.println("行驶中"); }catch(InterruptedException e){ e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms"); } }
对于这种情景,我们可以创建一个代理来专门实现计时功能,如下:
package com.proxy; public class CarProxy1 extends Car{ @Override public void move() { long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶"); super.move(); long endTime = System.currentTimeMillis(); System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms"); } }
此时在Car类中的move方法可以只行驶,将计时功能交给代理去实现,Car类中的move方法代码如下:
public void move() { try{ Thread.sleep(new Random().nextInt(1000)); System.out.println("行驶中"); }catch(InterruptedException e){ e.printStackTrace(); } }
现在我们要访问汽车时不用再直接实例化Car类,而是通过访问它的代理:CarProxy1实例,如下:
package com.proxy; public class Test { public static void main(String []args){ CarProxy1 car = new CarProxy1(); car.move(); } }
以上是通过继承的方式实现,还可以通过组合的方式实现,即不通过继承Car类,而是通过包装Car类来调用Car实例的行驶功能。代码如下:
package com.proxy; public class CarProxy2 implements Moveable{ private Car car; public CarProxy2(Car car) { super(); this.car = car; } @Override public void move() { long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶"); car.move(); long endTime = System.currentTimeMillis(); System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms"); } }
通过组合方式创建的代理类,在实例化代理前,需要先实例化Car对象,代码如下:
package com.proxy; public class Test { public static void main(String []args){ Car car = new Car(); CarProxy2 carProxy2 = new CarProxy2(car); carProxy2.move(); } }
与适配器模式中的类的适配器模式、对象的适配器模式相似,代理模式中组合方式比继承方式更灵活,推荐使用组合方式。比如再对Car实现日志记录等功能,使用继承方式则要再新建一个代理继承CarProxy1,之后再添加功能就要无限的向下继承,难于维护。
动态代理
上例中对汽车创建了一个时间记录代理,那么如果我们要对火车、飞机都记录时间呢?就需要分别对火车、飞机创建代理类。我们需要一个方法来把记录时间这个代理抽离出来,这样当需要对不同的交通工具实现记录时间功能的时候,直接运用这个抽离出的代理,这样可大大减少代码的重用率。这就引出了动态代理。
动态代理就是动态产生代理,实现对不同类,不同方法的代理。下面来看JDK动态代理。以下为JDK动态代理的实现机制:
如图所示,JDK的动态代理其实就是在代理类ProxySubject与被代理类RealSubject之间加入了一个ProxyHandler类,这个ProxyHandler类实现了InvocationHandler接口,它充当事务处理器,比如类似于上面给汽车计时的日志处理,时间处理等事务都是在这个ProxyHandler类中完成的。
InvocationHandler接口源码如下:
package java.lang.reflect; public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
其中第一个参数proxy代表代理对象;第二个参数method代表被代理的方法;第三个参数代表该方法的参数数组。
JDK动态代理实现步骤:
创建一个实现InvocationHandler的类,必须实现invoke方法
创建被代理的类和接口
调用Proxy的静态方法,创建一个代理类
通过代理调用方法
1,创建一个实现InvocationHandler的类,必须实现invoke方法:
package com.jdkproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class TimeHandler implements InvocationHandler { private Object object; public TimeHandler(Object object) { super(); //被代理的对象 this.object = object; } /** * proxy: 代理对象 * method: 被代理对象的方法 * args:方法的参数 * return: 调用方法的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶"); method.invoke(object); long endTime = System.currentTimeMillis(); System.out.println("汽车行驶结束,行驶时间为:"+(endTime-startTime)+" ms"); return null; } }
步骤 2,3,4,以下为Test类:
package com.jdkproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import com.proxy.Car; import com.proxy.Moveable; public class Test { public static void main(String[]args){ Car car = new Car(); InvocationHandler h = new TimeHandler(car); Class<?> cls = car.getClass(); /** * loader:类加载器 * interfaces:实现的接口 * h InvocationHandler */ Moveable m = (Moveable) Proxy.newProxyInstance( cls.getClassLoader(), cls.getInterfaces(), h); m.move(); } }
运行结果:
CGLIB动态代理:
CGLIB代理实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。主要使用于改造遗留系统,这些系统一般不会继承特地的接口。
cglib动态代理实现需要导入相关包,此处导入的为cglib-nodep-2.1_3.jar,然后需要创建事务处理器类,并实现MethodInterceptor接口,事务处理器类CglibProxy源码如下:
package com.cjlibproxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //设置创建子类的类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } /** * 拦截所有目标类方法的调用 * obj 目标类的实例 * m 目标方法的反射对象 * args 方法的参数 * proxy 代理类的实例 */ @Override public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable { //代理的业务逻辑 System.out.println("代理:开车"); //代理类调用父类的方法 proxy.invokeSuper(obj, args); //代理的业务逻辑 System.out.println("代理:到了"); return null; } }
有一个火车类,并没有实现接口:
package com.cjlibproxy; public class Train { public void move(){ System.out.println("火车行驶中..."); } }
测试类:
package com.cjlibproxy; public class Test { public static void main(String[]args){ CglibProxy proxy = new CglibProxy(); Train t = (Train) proxy.getProxy(Train.class); t.move(); } }
测试结果:
总结: JDK动态代理和CGLIB动态代理的使用都很简单,如果能理解内部的具体实现流程才能更深刻的理解动态代理,关于JDK动态代理的模拟实现会在之后文章补充。
标识类型模式
定义一个不包含任何方法的接口,用它仅仅来表示一种抽象类型。所有实现该接口的类意味着属于这种类型。比如定义一个Food接口,其中不包含任何方法:
public interface Food{}//实现该接口的类都是食物类型
鱼肉:
public class Fish implements Food{...}
进食方法:
public void eat(Food food){...}
进食:
Food fish = new Fish();//Fish实现了Food接口,标识其食物类型 eat(fish);//合法 Book book = new Book();//Book未实现Food接口 eat(book);//编译错误
所谓标识类型模式就是借助Java编译器来对传给eat()方法的food参数进行语义上的约束。Food接口被称为标识类型接口,这种接口没有任何方法,仅代表一种抽象类型。在JDK中,有如下两个典型的表示类型接口。
java.io.Serializable接口:实现该接口的类的实例可以被序列化
java.io.Remote接口:实现该接口的类的实例可以作为远程对象
常量接口模式
在一个软件系统中会使用一些常量,一种流行的做法是把相关的常量放在一个专门的常量接口中定义,例如:package com.FinalInterface; public interface MyConstants { public static final double MATH_PI = 3.1415926; public static final double MATH_E = 2.71828; }
以下Circle类需要访问以上MATH_PI常量,一种方式是采用直接访问方式,如下:
package com.FinalInterface; public class Circle { private double r;//半径 public Circle(double r){ this.r = r; } public double getCircumference(){ return 2 * r * MyConstants.MATH_PI; } }
在JDK1.5中引入了”import static“语句,它允许类A直接访问另一个接口B或类B中的静态常量,而不必指定接口B或类B的名字,而且类A无须实现接口B或者继承类B。如下:
package com.FinalInterface; import static com.FinalInterface.MyConstants.*; public class Circle { private double r;//半径 public Circle(double r){ this.r = r; } public double getCircumference(){ return 2 * r * MATH_PI; } }
import static 语句既可以简化编程,又能防止Circle类继承并公开MyConstants中的静态常量。
结合上我的上一篇:与接口相关的设计模式(1):定制服务模式和适配器模式详解,一共记录了与接口相关的5种设计模式,分别是定制服务模式、适配器模式、代理模式、标识类型模式以及常量接口模式。接口是构建松耦合的软件系统的重要法宝。接口的优势在于一个类可以实现多个接口,接口获得这一优势是以不允许为任何方法提供实现作为代价的(暂不考虑JAVA8的Default方法)。
我们可以把接口作为系统中最高层次的抽象类型。站在外界使用者(另一个系统)的角度,接口向使用者承诺系统能提供哪些服务;站在系统本身的角度,接口指定系统必须实现哪些服务。系统之间通过接口进行交互,这可以提高系统之间的松耦合。
至于抽象类呢,它用来定制系统中的扩展点。可以把抽象类看作介于”抽象“和”实现“之间的半成品。抽象类力所能及的完成了部分实现,但还有一些功能有待于它的子类去实现。
相关文章推荐
- linux环境下GDB与core dump调试程序方法
- 九度 OJ 1115:数字求和
- 完整java开发中JDBC连接数据库代码和步骤
- 会场安排问题
- 学习进度表
- kafka相关应用
- Error:Extjs 创建panel时,属性被覆盖
- 5-9 用天平找小球 (10分)
- 内嵌类在构造函数中的初始化
- 博客迁移通知
- TSS详解 ——《x86汇编语言:从实模式到保护模式》读书笔记33
- 5-8 超速判断 (10分)
- uva 11988 Broken Keyboard (a.k.a. Beiju Text)
- BP神经网络
- nyoj 1239 引水工程 河南省ACM2015年省赛D 题
- 第9章 结构型模式—桥接模式
- Intuitive Scrolling Interfaces with CSS Scroll Snap Points
- CentOS 7之Systemd详解之服务单元设置system.service
- 5-7 12-24小时制 (15分)
- 《java入门第一季》之LinkList模拟桟结构案例