13-红酒经销:代理模式
13.1 红酒代理商
现在很多商品在全国各地都有代理经销商,这为我们的生活带来很大便利。例如,现在我们想要喝红酒,不必满大街去找红酒生产工厂,只要找到超市或者红酒厂家的代理经销商就行了。如果有你想买的红酒,直接就可以买到,如果没有,代理商可以从厂家进货,等货来了再通知你。下面,我们就以此为例,来看看代理模式是怎么实现的。
13.2 模式定义
代理模式(Proxy Pattern),是软件设计模式中的一项基本技巧,在代理模式中,有两个对象参与处理同一请求,接收的请求由代理对象委托给真实对象处理,代理对象控制请求的访问,它在客户端应用程序与真实目标对象之间起到了一个中介桥梁的作用。代理模式使用对象聚合代替继承,有效地降低了软件模块之间的耦合度。
在代理模式中,涉及的角色有以下几类:
1)抽象角色:声明真实对象和代理对象的共同接口。
2)真实角色:真正处理请求的目标对象。
3)代理角色:代理对象角色内部含有真实对象的引用,从而代理对象可以将请求转为真实对象处理。同时,代理对象在执行真实对象操作的前后还可以添加附加操作。
13.3 模式实现
13.3.1 创建红酒生产厂商
1. 红酒工厂接口——IRedWine
首先,创建生产红酒接口“IRedWine”,该接口含有两个方法:生产红酒和销售红酒。
package com.demo.real; /** * Created by Daniel on 2018/3/21. * 红酒接口 */ public interface IRedWine { //生产红酒方法 public void product(); //销售红酒方法 public void sell(); }
2. 红酒工厂实现——RealRedWineFactory
package com.demo.real.impl; import com.demo.real.IRedWine; /** * Created by Daniel on 2018/3/21. * 真正的生产红酒工厂 */ public class RealRedWineFactory implements IRedWine{ //生产红酒方法 @Override public void product() { System.out.println("红酒工厂生产红酒……"); } //销售红酒方法 @Override public void sell() { System.out.println("红酒工厂销售红酒……"); } }
13.3.2 到红酒工厂购买红酒
红酒工厂已经建立起来了,现在你可以直接到工厂买到想要的酒了。
package com.demo; import com.demo.real.IRedWine; import com.demo.real.impl.RealRedWineFactory; /** * Created by Daniel on 2018/3/21. * 主应用程序 */ public class Client { public static void main(String[] args) { //创建红酒生产工厂 IRedWine redWine = new RealRedWineFactory(); //工厂生产红酒 redWine.product(); //工厂销售红酒 redWine.sell(); } }
运行结果如下:
13.3.3 创建红酒代理商
到红酒生产工厂购买红酒显然是不现实的做法,下面,我们创建红酒代理商类“RedWineProxy”,在该类中含有红酒工厂的对象引用,如果代理类能够解决(有客户想要买的酒),就直接交易,不能解决则交给真实对象红酒工厂处理。
package com.demo.proxy; import com.demo.real.IRedWine; /** * Created by Daniel on 2018/3/21. * 红酒代理商 */ public class RedWineProxy implements IRedWine{ //真正的红酒生产商 private final IRedWine redWine; //代理商出售红酒的权限 private final boolean permission = true; //默认构造方法 public RedWineProxy(IRedWine redWine) { this.redWine = redWine; } //代理商生产红酒方法(代理商不生产红酒,从真正的生产工厂拿酒销售) @Override public void product() { //判断代理商是否具有红酒代理权 if (this.permission){ //代理商有权卖红酒,是合法的 System.out.println("[这是合法的红酒代理商]"); System.out.println("代理商接到订单,通知工厂生产……"); this.redWine.product(); }else { System.out.println("[这是非法的红酒代理商!]"); } } //代理商销售红酒方法 @Override public void sell() { if (this.permission){ this.redWine.sell(); System.out.println("代理商从工厂拿到批发价红酒,然后以代理价销售……"); }else { System.out.println("[这是非法的红酒代理商!]"); } } }
可以看到,在红酒代理商中,构造方法将真实的红酒生产厂商对象实例引入。
13.3.4 到红酒代理商处购买红酒
package com.demo; import com.demo.proxy.RedWineProxy; import com.demo.real.IRedWine; import com.demo.real.impl.RealRedWineFactory; /** * Created by Daniel on 2018/3/21. * 应用程序 */ public class Client2 { public static void main(String[] args) { //创建真实的红酒工厂对象实例 IRedWine realRedWineFactory = new RealRedWineFactory(); //获得代理对象实例 IRedWine redWineProxy = new RedWineProxy(realRedWineFactory); //代理商生产红酒(其实真正生产的是工厂) redWineProxy.product(); //代理商销售红酒(批发价拿货,然后以代理价出售) redWineProxy.sell(); } }
运行结果如下:
上面红酒代理商的例子只是一个简单的代理模式的应用。其实,代理模式的应用不止这么简单,还有动态代理和面向切面编程(AOP)的内容,而且应用得非常广泛。例如,目前非常流行的SpringMVC框架中就大量应用了AOP技术。
13.4 设计原则
1. 延迟加载,提高系统效率
一个系统,特别是一个规模比较大的系统,如果在系统初始化的时候加载资源过多,会造成用户等待时间过长,容易引起用户使用的不满。代理模式具有延迟处理的效果,代理对象和真实对象具有相同的接口,当我们进行某一项具体功能调用时,可以使用代理对象预先加载资源,初始化数据,初始化成功后再对真实对象进行调用,这样就免去了用户因等待时间长而造成抱怨,提高了系统的访问效率。代理模式的另一个优点是,可以在调用真实对象处理之前进行一系列的校验工作,有效地将真实对象的功能进行分离,降低模块的耦合度。如果新增了功能处理,则无须修改真实对象处理,只修改代理对象功能即可实现。
13.5 使用场合
1)远程代理(RemoteProxy)为一个对象在不同的地址空间提供局部代理。
2)虚拟代理(VirtualProxy)中,若一个对象的创建非常耗时,可通过代理对象去调用,在真实对象创建前,返回一个假的调用,等真实对象创建好了,这时返回给客户端的就是一个真实对象的相应方法调用。
3)保护代理(ProtectionProxy)控制对原始对象的访问。
4)智能指引(SmartReference)取代了简单的指针,它在访问对象时执行一些附加操作。
以上情况是代理模式的各种使用场合。代理模式的使用是非常普遍的,它在处理一些延迟加载和对原始对象的访问控制上显得非常有效。
1. 扩展:Java SDK中的代理模式
在我们的示例中,讲解的是简单的静态代理模式的应用,其实代理模式的应用非常广泛,在JDK中就为我们引入了动态代理技术。所谓动态代理,就是一个系统在没有统一接口的情况下,在运行时,动态地对那些接口不同的对象提供代理支持。动态代理的核心是java.lang.reflect.InvocationHandler接口,要使用动态代理就必须实现该接口。这个接口的委派任务是在invoke(Object proxy, Method m, Object[] args)方法里实现的。
JDK中的动态代理是在运行时生成代理对象, 在使用java.lang.reflect.Proxy生成动态代理对象时必须提供一组代理对象需要实现的接口,以及实现java.lang.reflect.InvocationHandler接口的真实处理对象handler给它,然后,通过Proxy.newProxyInstance的调用,动态生成的代理对象就是实现了提供的这些接口,当然,可以将该动态代理对象作为其中的任意一种接口来使用。需要注意的是,这个生成的动态代理对象不会做实质性的工作,实际的工作是由真实处理对象handler来执行的。
2. 动态代理实现
1)新建“ITarget”代理接口,其中含有一个operation接口方法。
package com.demo.dynamic; /** * Created by lsq on 2018/3/25. * 要代理的接口 */ public interface ITarget { //操作方法 public void operation(); }
2)新建“TargetImpl”真实处理对象实现类,注意,因为是使用动态代理,所以不需要实现ITarget接口,而是实现java.lang.reflect.InvocationHandler接口。
package com.demo.dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * Created by lsq on 2018/3/25. * 实际的处理类 */ public class TargetImpl implements InvocationHandler{ //实现方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("---method:"+method.getName()); System.out.println("--动态代理实现了!"); return null; } }
我们在实际的处理对象中,打印了代理方法名称和一些静态文字。
到目前为止,ITarget接口和TargetImpl实现类还没有任何关联,下面,我们就在客户端应用程序中动态代理ITarget接口。新建DynamicClient客户端应用程序如下:
import com.demo.dynamic.ITarget; import com.demo.dynamic.TargetImpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * Created by lsq on 2018/3/25. * 动态代理客户端应用程序 */ public class DynamicClient { public static void main(String[] args) { //真实处理对象 InvocationHandler handler = new TargetImpl(); //创建代理类实例对象 ITarget target = (ITarget)Proxy.newProxyInstance(ITarget.class.getClassLoader(), new Class[]{ITarget.class}, handler); //操作方法 target.operation(); } }
在客户端应用程序中,我们首先创建了一个真实的处理对象handler,当然了,其类型是InvocationHandler。然后开始动态地创建代理对象,代理接口就是ITarget(生成的动态代理对象的类型就是ITarget类型),实际的处理对象则是handler。最后,我们调用动态代理对象的operation方法。运行程序,结果如下:
从结果可以看出,我们的动态代理已经生效了。没有一个具体的代理实现类,完全通过动态设置就可以达到动态代理的目的。