代理模式Proxy
代理模式(Proxy)为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。
代理模式的作用
- 在某些情况下,一个客户类不想或者不能直接引用一个对象,而代理类对对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 代理类除了是客户类和委托类的中介之外,我们还可以对被代理类做增强(通过给代理类增加额外的功能来扩展委托类的功能),我们这样做只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。
举个栗子
现在有一个游戏工厂接口GameFactory,抽象了一个制造游戏机的方法make():
/**
* 游戏工厂类
*/
public interface GameFactory {
/**
* 制造游戏机
*/
void make();
}
这个接口有两个实现类:制造Ps4的工厂:Ps4Factory,和制造XBox的工厂XBoxFactory
/**
* 功能描述:Ps4工厂
*/
public class Ps4Factory implements GameFactory {
@Override
public void make() {
System.out.println("-生产了一个Ps4游戏机-");
}
}
/**
* 功能描述:XBox工厂
*
* @author abner
* @version 1.0
* @since 2020/6/9 20:27
*/
public class XBoxFactory implements GameFactory {
@Override
public void make() {
System.out.println("-生产了一个XBox游戏机-");
}
}
so,如果消费者想买游戏机,可以直接找对应的游戏机生产厂商购买,像这样:
/**
* 功能描述:消费者
*/
public class Consumer {
public static void main(String[] args) {
//直接找不同的游戏机工厂买游戏机
Ps4Factory ps4Factory = new Ps4Factory();
ps4Factory.make();
XBoxFactory xBoxFactory = new XBoxFactory();
xBoxFactory.make();
}
}
打印结果:
-生产了一个Ps4游戏机-
-生产了一个XBox游戏机-
but,有时候,消费者没有办法直接联系到厂商,比如国内访问不了国外购物网站,这个时候可以委托代购去购买(代理)。
静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同的父类。
使用静态代理购买游戏机:
创建一个静态代理:游戏机代理商,和代理类一样也实现接口的方法
/**
* 功能描述:静态代理:游戏机代理商
*/
public class StaticProxy implements GameFactory {
private GameFactory gameFactory;
/**
* 构造方法:传入被代理对象GameFactory
* @param gameFactory 被代理对象GameFactory
*/
public StaticProxy(GameFactory gameFactory) {
this.gameFactory = gameFactory;
}
@Override
public void make() {
//前置增强
doSomeThingBefore();
//执行被代理类的方法
gameFactory.make();
//后置增强
doSomeThingAfter();
}
private void doSomeThingBefore() {
System.out.println("在方法执行之前做点什么");
}
private void doSomeThingAfter() {
System.out.println("在方法执行之后做点什么");
}
}
so,现在可以使用静态代理去买游戏机了:
/**
* 功能描述:消费者
*/
public class Consumer {
public static void main(String[] args) {
//找静态代理买游戏机
StaticProxy staticPs4Proxy = new StaticProxy(new Ps4Factory());
staticPs4Proxy.make();
StaticProxy staticXBoxProxy = new StaticProxy(new XBoxFactory());
staticXBoxProxy.make();
}
}
打印结果:
在方法执行之前做点什么
-生产了一个Ps4游戏机-
在方法执行之后做点什么
在方法执行之前做点什么
-生产了一个XBox游戏机-
在方法执行之后做点什么
静态代理总结
- 优点:不修改目标对象的功能前提下,对目标功能进行扩展。
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类被创建,类太多;有时候接口会增加其它方法,但代理类也必须去实现它,哪怕对代理类本身没有任何作用。同时,一旦接口增加方法,目标对象和代理对象都要维护,增加维护成本。
动态代理
代理类在程序运行时创建的代理方式被称为动态代理,这种情况下,代理类并不是在Java代码中事先定义好的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
使用静态代理购买游戏机:
创建一个动态代理,实现InvocationHandler接口。
InvocationHandler接口定义了标准化的代理流程
动态代理给我们提供了2个东西:
proxy:生成代理对象InvocationHandler:标准化的代理流程
/**
* 功能描述:动态代理:
*/
public class DynamicProxy implements InvocationHandler {
/**
* 被代理对象
*
* 在运行时动态传入进来,传给我什么对象,就代理什么对象
* 而不是像静态代理一样,运行前指定了代理的对象类型,只能代理那一种或几种类型的对象
*/
private Object target;
/**
* target的Get方法
* @return 被代理对象
*/
public Object getTarget() {
return target;
}
/**
* target的Set方法
* @param target 被代理对象
*/
public void setTarget(Object target) {
this.target = target;
}
/**
* 生成代理类的实例-代理对象
* @return 代理对象
*/
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增强
doSomeThingBefore();
//执行被代理类的方法
Object o = method.invoke(target, args);
//后置增强
doSomeThingAfter();
return null;
}
private void doSomeThingBefore() {
System.out.println("在方法执行之前做点什么");
}
private void doSomeThingAfter() {
System.out.println("在方法执行之后做点什么");
}
}
使用动态代理买游戏机:
/**
* 功能描述:消费者
*/
public class Consumer {
public static void main(String[] args) {
//找动态代理买游戏机
DynamicProxy dynamicProxy = new DynamicProxy();
//1.设置被代理的类:Ps4工厂
dynamicProxy.setTarget(new Ps4Factory());
//2.获取代理类的实例
GameFactory ps4Factory1 = (GameFactory) dynamicProxy.getProxy();
ps4Factory1.make();
}
}
打印结果:
在方法执行之前做点什么
-生产了一个Ps4游戏机-
在方法执行之后做点什么
代理服务于多种类型
在上面,都是只代理了一个接口GameFactory,但现在我又创建了一个接口BookFactory,它里面只有一个印刷书本的抽象方法print():
/**
* 书本工厂类
*/
public interface BookFactory {
/**
* 印刷书本
*/
void print();
}
同样,BookFactory也有一个实现类:
/**
* 功能描述:科学类书本印刷厂
*/
public class SinceBookFactory implements BookFactory {
@Override
public void print() {
System.out.println("印刷了一本科学书籍...");
}
}
如果我的代理类要同时代理这两个接口,该怎么办呢?
- 静态代理需要改成这样:
再实现一个BookFactory接口,并重写它的print()方法
/**
* 功能描述:静态代理:游戏机代理商&书本代理商
*/
public class StaticProxy implements GameFactory, BookFactory {
private GameFactory gameFactory;
private BookFactory bookFactory;
/**
* 构造方法:传入被代理对象GameFactory
* @param gameFactory 被代理对象GameFactory
*/
public StaticProxy(GameFactory gameFactory) {
this.gameFactory = gameFactory;
}
/**
* 构造方法:传入被代理对象BookFactory
* @param bookFactory 被代理对象BookFactory
*/
public StaticProxy(BookFactory bookFactory) {
this.bookFactory = bookFactory;
}
@Override
public void make() {
//前置增强
doSomeThingBefore();
//执行被代理类的方法
gameFactory.make();
//后置增强
doSomeThingAfter();
}
@Override
public void print() {
//前置增强
doSomeThingBefore();
//执行被代理类的方法
bookFactory.print();
//后置增强
doSomeThingAfter();
}
private void doSomeThingBefore() {
System.out.println("在方法执行之前做点什么");
}
private void doSomeThingAfter() {
System.out.println("在方法执行之后做点什么");
}
}
使用静态代理买书:
/**
* 功能描述:消费者
*/
public class Consumer {
public static void main(String[] args) {
//找静态代理买游戏机
StaticProxy staticPs4Proxy = new StaticProxy(new Ps4Factory());
staticPs4Proxy.make();
StaticProxy staticXBoxProxy = new StaticProxy(new XBoxFactory());
staticXBoxProxy.make();
//找静态代理买书
StaticProxy staticBookProxy = new StaticProxy(new ScienceBookFactory());
staticBookProxy.print();
}
}
打印结果:
在方法执行之前做点什么
-生产了一个Ps4游戏机-
在方法执行之后做点什么
在方法执行之前做点什么
-生产了一个XBox游戏机-
在方法执行之后做点什么
在方法执行之前做点什么
印刷了一本科学书籍...
在方法执行之后做点什么
- 对于动态代理:
由于动态代理的代理对象是在运行时动态创建, 所以我们可以代理任何类型的对象,只要把被代理的对象传给它就行了。
现在,不需要改任何代码,除了游戏机,我还可以向动态代理买书了!
使用动态代理买书:
/**
* 功能描述:消费者
*/
public class Consumer {
public static void main(String[] args) {
//找动态代理买游戏机
DynamicProxy dynamicProxy = new DynamicProxy();
//1.设置被代理的类:Ps4工厂
dynamicProxy.setTarget(new Ps4Factory());
//2.获取代理类的实例
GameFactory ps4Factory1 = (GameFactory) dynamicProxy.getProxy();
ps4Factory1.make();
//找动态代理买书
//1.设置被代理的类:科学类书印刷厂
dynamicProxy.setTarget(new SinceBookFactory());
//2.获取代理类的实例
BookFactory bookFactory = (BookFactory) dynamicProxy.getProxy();
bookFactory.print();
}
}
打印结果:
在方法执行之前做点什么
-生产了一个Ps4游戏机-
在方法执行之后做点什么
在方法执行之前做点什么
印刷了一本科学书籍...
在方法执行之后做点什么
总结
动态代理VS静态代理
- 静态代理类和委托类实现了相同的接口,代理类通过委托实现了相同的方法。这样就出现了大量的代码重复。
- 静态代理对象只服务于一种类型的对象,如果要服务多类型的对象,势必要为每一种对象都进行代理(
implement interface1,interface2...)。静态代理在程序规模稍大时就无法胜任了。