代理模式概述
给实际对象生成一个代理对象,将代理对象交给用户,避免用户操作实际对象。
可以做到不修改实际对象的代码,而为实际对象的方法添加新功能。比如在方法前后打印时间可以知道该方法的执行耗时。
静态代理
在编译期间就已经确定了代理对象和实际对象之间的关系。代理对象是在编译期间就生成的。
例子
小明想买个美版的mac,但是他没渠道,所以只能找一个代购去帮忙购买。
解析
-
要执行的动作是购买mac
-
实际对象是小明
-
代理对象是代购
代码实现
首先,声明一个动作购买mac。
public interface Action {
void buyMac();
}
其次,创建一个实际对象小明,并让其实现上面的购买mac动作。
public class XiaoMing implements Action {
@Override
public void buyMac() {
System.out.println("小明购买mac");
}
}
然后,创建一个代理对象代购,也让其实现上面的购买mac动作。
public class MyProxy implements Action {
private Action target;
public MyProxy(Action target) {
// 构造函数需要告诉代购,是谁需要代购
this.target = target;
}
@Override
public void buyMac() {
target.buyMac();
}
}
最后,实际使用。
MyProxy myProxy = new MyProxy(new XiaoMing());
myProxy.buyMac();
使用代理模式的原因
上面的代码看上去似乎有点多余,明明可以只创建一个小明,然后里面在声明一个购买mac的方法就可以,为什么还要多此一举的声明一个接口,创建一个代理对象呢?
假如现在新增一个需求:需要记录购买了多少台mac,如果超过了10台,那么就不继续代购了,因为容易被查水表/狗头。
那该怎么实现呢?假如没有使用代理的方式,那么代码可能是这样的:
首先,创建一个Accountant类,用于管理数量。
public class Accountant {
// 记录购买的mac数量
private static int macCount = 0;
/**
* 购买mac数量+1
*/
public static void addNewMac(){
macCount++;
}
/**
* 获取当前购买了多少台mac
* @return int
*/
public static int getMacCount(){
return macCount;
}
}
然后,在每次购买mac的时候,都调用Account
类的方法进行判断。
public class XiaoMing implements Action {
@Override
public void buyMac() {
if (Accountant.getMacCount() >= 10){
System.out.println("已购买数量超标");
}else {
System.out.println("小明购买mac");
Accountant.addNewMac();// 已购mac数量+1
}
}
}
这样看上去很美好不是吗?
假如此时又有一个小红需要购买mac,会再创建一个XiaoHong
。
public class XiaoHong implements Action {
@Override
public void buyMac() {
if (Accountant.getMacCount() >= 10){
System.out.println("已购买数量超标");
}else {
System.out.println("小红购买mac");
Accountant.addNewMac();// 已购mac数量+1
}
}
}
假如此时小黑、小白、小兰也想购买mac呢?怎么办?继续创建XiaoHei
、XiaoBai
、XiaoLan
吗?
这样,每次在购买mac之前都需要写一次判断方法,太傻了。程序员最擅长什么?
把重复的动作变成自动的。
这个时候,使用代理模式来处理的话,只需要在代理对象中写一次判断就可以了。
public class MyProxy implements Action {
private Action target;
public MyProxy(Action target) {
// 构造函数需要告诉代购,是谁需要代购
this.target = target;
}
@Override
public void buyMac() {
if (Accountant.getMacCount() >= 10){
System.out.println("已购买数量超标");
}else {
target.buyMac();
Accountant.addNewMac();// 已购mac数量+1
}
}
}
看啊,人类的智慧!理论上少写了无数行重复代码!太厉害了!奥利给!
动态代理
在运行时,才确立代理对象和实际对象间的关系。代理对象是在运行时生成的。
例子
现在这个代购生意做大了,不但能代购mac了,还能代购手机、奶粉、球鞋等你能想到的一切东西。而且为了炫耀,他每次接到单都要发朋友圈。
解析
-
要执行的动作是代购手机、奶粉、球鞋等
-
实际对象会存在很多个
-
每次执行动作前都要发朋友圈
代码实现
创建若干个接口,分别有购买手机、购买奶粉、购买球鞋等。(这里为了减少代码量,只写了一个购买手机的例子)
public interface Action1 {
void bugPhone();
}
同样,创建实际对象(要购买手机的人),并实现上面的接口。
public class XiaoHei implements Action1 {
@Override
public void bugPhone() {
System.out.println("小黑购买phone");
}
}
现在就存在问题了,如果还像使用静态代理一样的方式去创建一个代理对象的话,那么类文件数量肯定会非常夸张。那有没有什么办法可以不创建类文件,但是又能生成类呢?人类的智慧是无穷的,动态代理就出来了!
// 1.实例化实际对象
final XiaoHei xiaoHei = new XiaoHei();
// 2.获取类加载器
ClassLoader loader = xiaoHei.getClass().getClassLoader();
// 3.获取所有接口class,这里的XiaoHei只实现了Action1.class
Class[] interfaces = xiaoHei.getClass().getInterfaces();
// 4.实例化出代理对象
Action1 proxy =(Action1) Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 代理对象每次执行方法时,都会调用到这里
broadcast();// 发朋友圈
return method.invoke(xiaoHei,objects);// 执行实际对象的方法
}
});
// 5.执行代理对象的方法
proxy.bugPhone();
使用动态代理的原因
通过上面的例子,可以看到,不需要再创建代理类文件了,直接在代码中生成了代理类,显著的减少了文件数量。