原文:https://www.imooc.com/article/24850
默课道可工程师学习地址:https://www.imooc.com/article/24850
介绍
代理模式中,在客户端与对象之间增加了一个代理层。客户端在进行访问时候,不是直接访问对象,而是访问代理。代理模式是一种结构型的设计模式。通过代理模式,可以解决直接访问对象带来的一些问题,并且可以进行访问控制。
代理模式的实现中,代理类中依赖实体类,但两者共同实现相同的接口。代理类中对应接口中,会调用实体类的对应接口。听上去与装饰器的实现一样,是的,两者的实现基本上是相同的,只是使用意义上的侧重点不同。两者的区别,会在文末的总结中进行分析。
代理模式的应用场景比较广泛,跟“代理”两个字沾边的,一般都会涉及代理模式。比如火车票代售站,对于乘客来说,他不需要去真正的火车站,而通过代售点,就可以实现买票取票的功能。比如VPN,由于众所周知的原因,我们不能访问youtube,那么我们可以设置一个VPN,通过VPN进行访问。再比如说windows的快捷方式,也是一种代理。还有就是Spring的AOP。
代理模式从类型上来说分为两种,静态代理和动态代理。所谓静态代理,就是在编译期间就确定的代理关系,一般是一对一的。而动态代理,是一对多的代理关系,是在运行时态才能确定的代理关系。动态代理的原理比较复杂,本篇文章只简单介绍动态代理的使用,在下一篇文章中,会从原理和代码的层面,对动态代理做专门的解读。
案例
静态代理
背景
以购买火车票为例。乘客(客户端)在购买火车票的时候,可以在代售点(代理类)进行购买(功能),而不需要花费很长的时间跑去火车站(真实类)进行购买(功能)。
实现
定义抽象接口
public interface shop {
void buy();
void take();
}
定义真实火车站
public class Station implements shop
{
@Override
public void buy() {
System.out.println("购买火车票");
}
@Override
public void take() {
System.out.println("领取火车票");
}
}
定义代售点
public class ProxyStation implements shop
{
private Station station = new Station();
@Override
public void buy() {
beforeBuy();
station.buy();
afterBuy();
}
private void beforeBuy() {
System.out.println("购买之前操作");
}
private void afterBuy() {
System.out.println("购买之后的操作");
}
@Override
public void take() {
beforeTake();
station.take();
afterTake();
}
private void afterTake() {
System.out.println("取票之前的操作");
}
private void beforeTake() {
System.out.println("取票之后的操作");
}
}
验证程序
public class Test {
public static void main(String args[])
{
ProxyStation proxyStation = new ProxyStation();
proxyStation.buy();
proxyStation.take();
}
}
运行结果
购买之前操作
购买火车票
购买之后的操作
取票之后的操作
领取火车票
取票之前的操作Process finished with exit code 0
动态代理
动态代理的场景是一对多的关系,即是一个代理类,多个被代理的类。
在动态代理中,不再关注向客户端屏蔽原始对象(客户端可以看到原始对象),而重点是对于一系列的原始实现类,能够对抽象中的方法进行统一的横向扩展(方法执行前/后的操作),而不需要为每一个原始实现类都创建一个代理。
动态代理的原理,会在下篇文章中进行分析。
案例
以汽车(抽象)行驶为例子,我们要统计多种车型(原始实现类)通过某段距离的用时。在汽车行驶前,需要进行开始计时的操作(功能扩展)。汽车行驶后,要进行结束计时的操作(功能扩展)。
实现
抽象接口
public interface Moveable {
void move() throws Exception;
void move_back() throws Exception;
}
定义实现类
import java.util.Random;
public class Car implements Moveable {
public void move() throws Exception {
Thread.sleep(new Random().nextInt(1000));
System.out.println("轿车行驶中…");
}
public void move_back() throws Exception {
Thread.sleep(new Random().nextInt(1000));
System.out.println("轿车向后行驶中…");
}
}
import java.util.Random;
public class Truck implements Moveable {
public void move() throws Exception {
Thread.sleep(new Random().nextInt(1000));
System.out.println("卡车行驶中…");
}
public void move_back() throws Exception {
Thread.sleep(new Random().nextInt(1000));
System.out.println("卡车向后行驶中…");
}
}
定义动态代理工具类
动态代理工具类需要实现InvocationHandler
接口,实现invoke
方法,通过反射实现对抽象接口方法的调用method.invoke(target, args);
。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
private Object target;
public TimeHandler(Object target) {
super();
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("汽车开始行驶…");
method.invoke(target, args);
long stopTime = System.currentTimeMillis();
System.out.println("汽车结束行驶…汽车行驶时间:" + (stopTime - startTime) + "毫秒!");
return null;
}
}
验证程序
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception{
Car car = new Car();
Class<?> cls = car.getClass();
InvocationHandler h = new TimeHandler(car);
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
m.move();
System.out.println("");
m.move_back();
System.out.println("");System.out.println("");
Truck truck = new Truck();
cls = truck.getClass();
h = new TimeHandler(truck);
m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
m.move();
System.out.println("");
m.move_back();
System.out.println("");System.out.println("");
}
}
运行结果
汽车开始行驶…
轿车行驶中…
汽车结束行驶…汽车行驶时间:200毫秒!汽车开始行驶…
轿车向后行驶中…
汽车结束行驶…汽车行驶时间:681毫秒!汽车开始行驶…
卡车行驶中…
汽车结束行驶…汽车行驶时间:492毫秒!汽车开始行驶…
卡车向后行驶中…
汽车结束行驶…汽车行驶时间:338毫秒!Process finished with exit code 0
总结
代理模式优缺点:
- 静态代理模式:优点是具有较好的扩展性,屏蔽原始实现类;缺点是增加了中间层可能会导致性能下降。
- 动态代理模式:优点是能对多个原始实现类进行统一的功能扩展;缺点是增加了中间层可能会导致性能下降。
代理模式与装饰器模式的区别:
- 代理模式强调对于访问的控制,在调用实体类的方法之前或者之后,会添加一些操作,是扩展的广度。装饰器模式强调对于方法的加强,扩展的深度。打个比方,张无忌学习乾坤大挪移。装饰器模式,则增加其深度,从第3层境地学习到第4层境地,注重功能的加强。代理模式则是让其在学习乾坤大挪移之前,先学习一下九阳神功,两种武功能够有所联系。从外界看来,都是武功的增强,但是一个是深度,一个是广度。
- 代理模式是代理,装饰器模式是装饰,两者在对原始对象的可见性上是不同的。代理模式中,在客户看来,无法感知到原始对象,只能接触代理对象。装饰器模式中,客户看来,是能够感知到原始对象的。