概念:一个类代表另一个类的功能。这种类型的设计模式属于结构型模式(假装写个概念,别处Copy过来的,反正光看这句话有点云里雾里的,我还是喜欢从思路出发,能举一反三才算是理解)。
假如我们有这样一个需求,我们现在有一个已经上线的车票购买系统,购票实现代码如下:
public class StationBuyTicket implements IBuyTicket {
@Override
public void buyTicket(double price) {
System.out.println(price + " yuan for a ticket");
}
}
StationBuyTicket stationBuyTicket = new StationBuyTicket();
stationBuyTicket.buyTicket(4.3);
我们需要为其添加是否购满足票条件判断,账户状态是否可以购票和日志统计等等功能已满足新的业务需求,我们该怎么做?
思路1、在以上buyTicket方法中,添加一大堆If else进行判断,如果条件满足继续购买,不满足直接返回。
这种方式能实现需求吗? 答案是不确定,且不说代码结构和干净的问题,如果这个接口代码你随意修改,没问题完全可以,但是如果这个购买接口的代码是三方的库,源码你没法修改呢?
思路2、既然源码没法修改,那我在调用此接口的时候进行判断,那就没问题了吧。
如果你这样想,恭喜你,方向对了。这里重点考虑的不是三方库我们没法修改,应该考虑的是解耦,减少不相关模块的耦合度,这只是一种编程思想,如果解耦了,以后修改或扩展购买接口,我们将更加方便。
注:高内聚,低耦合有助于程序的维护,单独的功能做成独立模块,整个系统由一个个模块通过接口组合而成,如果需要更新或者修改局部功能,只要修改一个接口的一个模块,不影响整个系统其他功能使用。
一、静态代理
接着以上的思路,我们需要做的是在接口外层加一层封装,以实现新增的日志流程等和原有的购买实现解耦。这里我们就可以用到代理模式了。举个例子,代理模式和车站售票和车票代卖点类似,车站卖车票和代理站卖票,代理站和车站卖的票是相同的,但是代理站还可以在卖票的同时,收取服务费等。车票和价格就相当于软件上的需求,所以代理的功能是大于被代理的功能的。毕竟如果功能相同,代理且不是多此一举。下面就用车票售卖来实现代理模式。
首先,既然是代理,那么代理需要卖和车站一样的车票,软件上来说就是功能需要一样,然后代理在车站的的基础上加点额外服务,这里我们就打个日志吧,那么在这种情况下,继承相同的父类是很好的选择
public interface IBuyTicket {
/**
* 购票
*/
void buyTicket(double price);
}
其次,继承接口实现车站代码
public class StationBuyTicket implements IBuyTicket {
@Override
public void buyTicket(double price) {
System.out.println(price + " yuan for a ticket");
}
}
第三,实现代理,代理也需要和车站有相同的接口实现,也要添加自己的功能需求
public class ProxyBuyTicket implements IBuyTicket {
private IBuyTicket iBuyTicket;
public ProxyBuyTicket(IBuyTicket iBuyTicket) {
this.iBuyTicket = iBuyTicket;
}
@Override
public void buyTicket(double price) {
//买票日志处理
System.out.println("Log: start by ticket.");
iBuyTicket.buyTicket(price);
System.out.println("Log: end by ticket.");
}
}
最后,该组装我们的代理了
StationBuyTicket stationBuyTicket = new StationBuyTicket();
ProxyBuyTicket proxyBuyTicket = new ProxyBuyTicket(stationBuyTicket);
proxyBuyTicket.buyTicket(4.3);
=============================输出结果=================================
Log: start by ticket.
4.3 yuan for a ticket
Log: end by ticket.
完成,看最后的输出结果,既调用的厂家的接口,又增加了代理商自己的需求。
总结思考:优秀的代码总会有良好的扩展性,下面来尝试一下增加其它种类的车票或者转化为机票呢。怎么操作? 增加新种类车票,修改父接口,修改代理和车站实现类。 新增机票呢,就需要重新创建父类接口,车站和代理代码了,这样扩展起来就略显复杂。
二、动态代理
那么,有没有其它方案简化这种新增功能的复杂度呢? 答案肯定是有的,Java 已经提供了Proxy类给予了我们很好的支持,这就是Java的动态代理。下面我们就来实现Java的动态代理怎么实现
首先,Proxy类实现必须是要提供接口,这里我们可以使用和上面相同的接口
其次,动态代理实现
public class DynamicBuyTicket {
private IBuyTicket iBuyTicket;
public DynamicBuyTicket(IBuyTicket iBuyTicket) {
this.iBuyTicket = iBuyTicket;
}
public IBuyTicket getProxyInstance() {
return (IBuyTicket) Proxy.newProxyInstance(iBuyTicket.getClass().getClassLoader(), iBuyTicket.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//买票日志处理
System.out.println("DynamicProxy Log: start Buy Ticket.");
Object result = method.invoke(iBuyTicket, args);
System.out.println("DynamicProxy Log: end Buy Ticket.");
return result;
}
});
}
}
第三,动态代理调用和输出
IBuyTicket stationBuyTicket = new StationBuyTicket();
DynamicBuyTicket dynamicBuyTicket = new DynamicBuyTicket(stationBuyTicket);
IBuyTicket iBuyTicket = dynamicBuyTicket.getProxyInstance();
iBuyTicket.buyTicket(5.4);
=============================输出结果=================================
DynamicProxy Log: start Buy Ticket.
5.4 yuan for a ticket
DynamicProxy Log: end Buy Ticket.
总结思考:这样如果我们需要添加更多的机票,船票,是不是就更简单了。但这种方式需要通过继承的方式实现,但是如果我们使用的第三方库,且对方没有继承或者没有提供父类的API呢,怎么办? Java动态代理就形同虚设了,没法使用。
三、其它三方代理
别急,Java Proxy实现不了的,还有很多牛逼哄哄的代理库可以用,比如CGLIB,javaassist,ASM。。。等等,具体实现方式,就动动手指自行百度了。