设计模式——代理模式与装饰器模式
代理模式
解决的问题:在直接访问对象时带来很大的开销。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
代理模式就相当于Windows 里面的快捷方式,它并不实现什么功能,而只是在中间加以控制;而装饰器模式为了增强功能。
Java中的典型示例:静态代理:hibernate中的session.load()方法;动态代理:SpringAOP
代码实现:
1> 创建一个接口:Image.java
public interface Image { void display(); }
2>创建两个接口的实现类
RealImage.java(假设该类实例化开销很大)
public class RealImage implements Image { private String fileName; public RealImage(String fileName){ this.fileName = fileName; loadFromDisk(fileName); // 当实例化RealImage类的时候,会执行这个方法,打印输出语句 } @Override public void display() { // 重写父类的display方法 System.out.println("Displaying " + fileName); } private void loadFromDisk(String fileName){ System.out.println("Loading " + fileName); } }
ProxyImage.java(代理类)
public class ProxyImage implements Image{ private RealImage realImage; private String fileName; public ProxyImage(String fileName){ // 实例化时并不直接实例化RealImage,而是实例化这个类(这样就由类代理了RealImage的display方法) this.fileName = fileName; } @Override public void display() { // 当调用display方法时,实例化RealImage,然后再调用display方法 if(realImage == null){ realImage = new RealImage(fileName); } realImage.display(); } }
3>当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。
public class ProxyPatternDemo { public static void main(String[] args) { Image image = new ProxyImage("test_10mb.jpg"); // 实例化代理类 image.display(); 调用方法 } }
静态代理与动态代理
静态代理:
静态代理就是我们上面写的内种方法,它的优缺点也很明显:
优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)。
缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。
即静态代理类只能为特定的接口服务。如想要为多个接口服务则需要建立很多个代理类
动态代理:
根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类
所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理
在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象
在运行时动态的在内存中(那肯定和反射有关)创建代理对象
好处是:可以不用改动态代理的代码,自动生成要求对象的代理对象
在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持
@Test public void test1(){ //获得动态的代理对象----在运行时 在内存中动态的为Target创建一个虚拟的代理对象 //objProxy是代理对象 根据参数确定到底是谁的代理对象 TargetInterface objProxy = (TargetInterface) Proxy.newProxyInstance( Target.class.getClassLoader(), //与目标对象相同的类加载器 new Class[]{TargetInterface.class}, new InvocationHandler() { //invoke 代表的是执行代理对象的方法 //method:代表目标对象的方法字节码对象 //args:代表目标对象的响应的方法的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("目标方法前的逻辑"); //通过反射执行目标对象的方法 Object invoke = method.invoke(new Target(), args); System.out.println("目标方法后的逻辑"); //retrun返回的值给代理对象 return invoke; } } ); objProxy.method1(); String method2 = objProxy.method2(); System.out.println(method2); }
装饰器模式
向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
作用:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
装饰器模式相当于孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
实现步骤:
1、增强类与被增强的类要实现统一接口
2、在增强类中传入被增强的类
3、需要增强的方法重写 不需要增强的方法调用被增强对象的
示例:解决request.getParameter()方法获取的中文乱码问题
public class EncodingFilter implements Filter{ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //被增强的对象 HttpServletRequest req = (HttpServletRequest) request; //增强对象 EnhanceRequest enhanceRequest = new EnhanceRequest(req); chain.doFilter(enhanceRequest, response); } } class EnhanceRequest extends HttpServletRequestWrapper{ // 1>与要增强的类(HttpServletRequest类)继承/实现同一个类/接口 private HttpServletRequest request; public EnhanceRequest(HttpServletRequest request) { // 2>传入要增强的类 super(request); this.request = request; } //3>对要增强的方法(getParaameter)重写 @Override public String getParameter(String name) { String parameter = request.getParameter(name);//乱码 try { parameter = new String(parameter.getBytes("iso8859-1"),"UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return parameter; } }
两者之间的区别***
装饰器模式关注于在一个对象上动态的添加方法(可以说他是继承的一个替代方案),然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。