装饰模式、代理模式
参考文献:
[1]Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides.Dedign Patterns Elements of Reusable Object-Oriented Software 北京:机械工业出版社,2010.07;
[2]程杰.大话设计模式 北京:清华大学出版社,2007.10;
目录:
1、装饰模式(Decorator):
2、代理模式(Proxy):
3、装饰模式和代理模式的区别:
4、装饰模式和策略模式的区别:
1、装饰模式(Decorator):
(1)定义:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
(2)结构图:
图1.1 结构图
Component:定义一个对象接口,可以给这些对象动态地添加职责。
Decorator:装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无须知道Decorator的存在的。
ConcreteDecorator:具体的装饰对象,起到给Component添加职责的功能。
ConcreteComponent:具体的对象,可以给这个对象添加一些职责。
(3)优点:
①比静态继承(多重继承)更灵活。
②避免在层次结构高层的类有太多的特征。可以定义一个简单的类,并且用Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。
(4)缺点:
①有许多小对象。这些对象仅仅在他们相互连接的方式上有所不同。
(5)适用场景;
①在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
②处理那些可以撤销的职责。
③当不能采用生成子类的方法进行扩充时。
(6)示例代码:
工程目录:
图1.2 工程结构图
①接口Shape.java
package com.remoa.decoratorPattern.component;
public interface Shape {
public void draw();
}
②ClothesShapeImp.java
package com.remoa.decoratorPattern.component;
public class ClothesShapeImp implements Shape{
@Override
public void draw() {
System.out.println("Clothes Shape:");
}
}
③TrousersShapeImp.java
package com.remoa.decoratorPattern.component;
public class TrousersShapeImp implements Shape{
@Override
public void draw() {
System.out.println("Trousers Shape:");
}
}
④抽象类ShapeDecorator.java
package com.remoa.decoratorPattern.decorator;
import com.remoa.decoratorPattern.component.Shape;
public abstract class ShapeDecorator implements Shape{
protected Shape shapeComponent;
public void shapeDecorator(Shape shape){
this.shapeComponent = shape;
}
public void draw(){
shapeComponent.draw();
}
}
⑤BlueShapeDecorator.java
package com.remoa.decoratorPattern.decorator;
public class BlueShapeDecorator extends ShapeDecorator{
@Override
public void draw() {
super.draw();
setBlueColor();
}
public void setBlueColor(){
System.out.println("Color: blue;");
}
}
⑥RedShapeDecorator.java
package com.remoa.decoratorPattern.decorator;
public class RedShapeDecorator extends ShapeDecorator{
@Override
public void draw() {
super.draw();
setRedColor();
}
public void setRedColor(){
System.out.println("Color: Red;");
}
}
⑦CottonShapeDecorator.java
package com.remoa.decoratorPattern.decorator;
public class CottonShapeDecorator extends ShapeDecorator{
@Override
public void draw() {
super.draw();
setMatrial();
}
public void setMatrial(){
System.out.println("Matrial: cotton;");
}
}
⑧Test.java
package com.remoa.decoratorPattern;
import com.remoa.decoratorPattern.component.ClothesShapeImp;
import com.remoa.decoratorPattern.component.Shape;
import com.remoa.decoratorPattern.component.TrousersShapeImp;
import com.remoa.decoratorPattern.decorator.BlueShapeDecorator;
import com.remoa.decoratorPattern.decorator.CottonShapeDecorator;
import com.remoa.decoratorPattern.decorator.RedShapeDecorator;
/**
* 为不同的形状装饰上不同的颜色,不能改变形状类。
* @author Remoa
*
*/
public class Test {
public static void main(String[] args) {
Shape clothes = new ClothesShapeImp();
RedShapeDecorator redShapeDecorator = new RedShapeDecorator();
CottonShapeDecorator cottonShapeDecorator = new CottonShapeDecorator();
redShapeDecorator.shapeDecorator(clothes);
cottonShapeDecorator.shapeDecorator(redShapeDecorator);
cottonShapeDecorator.draw();
System.out.println("---------------------------");
Shape trousers = new TrousersShapeImp();
BlueShapeDecorator blueShapeDecorator = new BlueShapeDecorator();
blueShapeDecorator.shapeDecorator(trousers);
blueShapeDecorator.draw();
}
}
运行结果:
图1.3 运行结果
2、代理模式(Proxy):
(1)定义:为其他对象提供一种代理以控制对这个对象的访问。
(2)结构图:
图2.1 结构图
Client:客户端,使用代理类和主题接口完成一些工作。
Subject:定义了RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。
Proxy:保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
RealSubject:定义Proxy所代表的真实主题,真正实现业务逻辑的类。
(3)应用场景:
①远程代理,也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。比如说 WebService,当我们在应用程序的项目中加入一个 Web引用,引用一个 WebService,此时会在项目中声称一个WebReference 的文件夹和一些文件,这个就是起代理作用的,这样可以让那个客户端程序调用代理解决远程访问的问题;
②虚拟代理,是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。这样就可以达到性能的最优化,比如打开一个网页,这个网页里面包含了大量的文字和图片,但我们可以很快看到文字,但是图片却是一张一张地下载后才能看到,那些未打开的图片框,就是通过虚拟代理来替换了真实的图片,此时代理存储了真实图片的路径和尺寸;
③安全代理,用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候;
④指针引用,是指当调用真实的对象时,代理处理另外一些事。比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它,或当第一次引用一个持久对象时,将它装入内存,或是在访问一个实际对象前,检查是否已经释放它,以确保其他对象不能改变它。这些都是通过代理在访问一个对象时附加一些内务处理;
⑤延迟加载,用代理模式实现延迟加载的一个经典应用就在 Hibernate框架里面。当 Hibernate加载实体 bean时,并不会一次性将数据库所有的数据都装载。默认情况下,它会采取延迟加载的机制,以提高系统的性能。Hibernate中的延迟加载主要分为属性的延迟加载和关联表的延时加载两类。实现原理是使用代理拦截原有的 getter 方法,在真正使用对象数据时才去数据库或者其他第三方组件加载实际的数据,从而提升系统性能。
(4)动态代理是指在运行时动态生成代理类。即代理类的字节码将在运行时生成并载入当前代理的ClassLoader。
动态代理相比静态代理的好处:
①静态代理中主题接口中方法有变动,则真实主题和代理类都要修改,不利于系统维护。
②动态代理可以在运行时指定代理类的执行逻辑,大大提升系统的灵活性。
生成动态代理类的方法很多,如JDK 自带的动态处理、CGLIB、Javassist或者 ASM库。
(5)示例代码:
工程目录:
图2.2 工程目录图
①接口BookProxy.java
package com.remoa.dynamicProxy.cglib;
public interface BookProxy {
public void addBook();
public void sendBook();
}
②BookProxyFactory.java
package com.remoa.dynamicProxy.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 方法拦截器,实现MethodInterceptor接口,在调用目标对象的方法时,就可以实现在调用方法之前、调用方法之中和调用方法之后对其进行控制。
* Enhancer允许为非接口类型创建一个代理对象。
* @author Remoa
* 此类的作用是创建增强代理,实现方法拦截。
*/
public class BookProxyFactory implements MethodInterceptor{
//要代理的原始对象
private Object target;
//创建代理对象
public Object getInstance(Object object){
this.target = object;
Enhancer enhancer = new Enhancer();
//设置代理目标
enhancer.setSuperclass(this.target.getClass());
//设置回调
//CallBack接口是一个空接口,它只表示这是一个回调,有常用到的子接口MethodInterceptor,FIxedValue,InvocationInterceptor等等。
//enhancer.setCallback(this)会调用下面重写的intercept方法。
enhancer.setCallback(this);
/*//使用FixedValue替换掉方法的返回值进行回调
enhancer.setCallback(new FixedValue(){
@Override
public Object loadObject() throws Exception {
String str = "Hello, I am remoa";
System.out.println(str);
return str;
}
});*/
//设置类加载器
enhancer.setClassLoader(this.target.getClass().getClassLoader());
return enhancer.create();
}
//回调方法
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
before();
arg3.invokeSuper(arg0, arg2);
after();
return null;
}
private void before(){
System.out.println("在方法被invoke之前-----------");
}
private void after(){
System.out.println("在方法被invoke之后-----------");
}
}
③BookProxyImp.java
package com.remoa.dynamicProxy.cglib;
//这个类并没有实现BookProxy接口
public class BookProxyImp {
public void addBook(){
System.out.println("增加图书的普通方法。");
}
public void sendBook(){
System.out.println("借出图书的普通方法。");
}
}
④TestCglib.java
package com.remoa.dynamicProxy.cglib;
public class TestCglib {
public static void main(String[] args) {
BookProxyFactory cglib = new BookProxyFactory();
BookProxyImp realSubject = new BookProxyImp();
BookProxyImp bookCglib = (BookProxyImp)cglib.getInstance(realSubject);
bookCglib.sendBook();
bookCglib.addBook();
}
}
运行结果:
图2.3 运行结果截图
①DBQuery.java
package com.remoa.dynamicProxy;
public interface DBQuery {
public String request();
}
③DBQueryImp.java
package com.remoa.dynamicProxy;
public class DBQueryImp implements DBQuery{
public DBQueryImp(){
try {
Thread.sleep(2000);//假设为数据库连接等所要花费时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String request() {
String str = "真实主题开始发送请求";
System.out.println(str);
return str;
}
}
③DBQueryProxy.java
package com.remoa.dynamicProxy;
public class DBQueryProxy implements DBQuery{
private DBQuery realSubject = null;
@Override
public String request() {
if(null == realSubject){
realSubject = new DBQueryImp();
}
return realSubject.request();
}
}
运行结果:
图2.4 运行结果截图
①DBQueryHandler.java
package com.remoa.dynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Spring AOP的原理就是java的动态代理机制。
* 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个Handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会转发为由InvocationHandler这个接口的invoke方法来进行调用。
* @author Remoa
*
*/
public class DBQueryHandler implements InvocationHandler{
DBQuery realQuery = null;
/**
* proxy:代理的真实对象
* method:调用真实主题的某个方法的Method对象
* args:调用真实主题某个方法时所接受的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke被调用了-----------");
if(null == realQuery){
realQuery = new DBQueryImp();
}
return realQuery.request();
}
//生成动态代理对象
public static DBQuery createProxy(){
/**
* 得到一个动态代理对象
* loader:定义由哪个classloader对象来生成的代理对象进行加载
* interfaces:一个Interfaces对象的数组,表示我将要给我代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口,这样就能调用这组接口中的方法了
* h:定义当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
*/
DBQuery proxy = (DBQuery)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{DBQuery.class}, new DBQueryHandler());
return proxy;
}
public static void main(String[] args) {
DBQuery dbqueryProxy = createProxy();
dbqueryProxy.request();
}
}
运行结果:
图2.5 运行结果截图
3、装饰模式和代理模式的区别:
(1)两种模式都描述了怎样为对象提供一定程度上的间接引用,proxy和decorator对象的实现部分都保留了指向另一个对象的指针,它们向这个对象发送请求。
(2)Proxy模式构成一个对象并为用户提供一致的接口,其不能动态地添加或分离性质,它也不是为递归组合而设计的。它的目的是,当直接访问一个实体不方便或不符合需要时,为这个实体提供一个替代者。例如,实体在远程设备上,访问受到限制或者实体是持久存储的。
(3)在Proxy模式中,实体定义了关键功能,而Proxy提供(或拒绝)对它的访问。在Decorator模式中,组件仅提供了部分功能,而一个或多个Decorator负责完成其他功能。Decorator模式适用于编译时不能(或不方便)确定对象的全部功能的情况。这种开放性使递归组合成为Decorator模式中一个必不可少的部分。
4、装饰模式和策略模式的区别:
(1)装饰模式将核心功能与装饰功能分开,可以动态地添加功能,是在主体逻辑的基础上的附加逻辑,允许向一个现有的对象添加新的功能,同时又不改变其结构;
(2)策略模式是定义一系列算法的方法,所有的算法功能相同,但是实现不同。更倾向于N选1的模式,根据条件选择相应的算法,但各种算法是互斥的;
(3)用一个装饰可以改变对象的外表,而策略模式可以改变对象的内核。装饰模式更符合设计的一项原则:少继承,多组合。