设计模式系列
创建型设计模式
Java 设计模式之静态工厂、工厂方法、抽象工厂和 Builder 模式的区别
结构型设计模式
前言
前面几篇文章,我们一直在聊 创建型设计模式,从本讲开始,我们来聊一聊 结构型设计模式。在 GOF 提出的 23 种设计模式中,结构型设计模式占有 7 席,分别为:代理模式、装饰模式、外观模式、享元模式、适配器模式、桥接模式、组合模式。
本讲的主题是 代理模式。代理模式,又被称为 “委托模式”,是结构型设计模式的重点,有些结构型设计模式,甚至可以认为是从代理模式优化而来的。
代理模式无论是在我们的程序中,还是在我们的生活中,都比较常见。比如打官司找的代理律师、买房租房找到的房屋代理、上网用的代理上网、室友同事帮我们买饭可以认为是买饭代理等。
什么是代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问。
使用场景:当无法或不想直接访问某个对象或访问某个对象存在困难时,可以通过一个代理对象来间接访问。为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。
通俗点来说,就是我不想干,或者干不了,找个代理让他干。比如说,打官司辩护我干不了啊,让律师干;租房子我不想找房东一个个问房子啥情况,让租房代理干;我懒我不想下去买饭,让同事带。
另外还有一点很重要,代理模式可以做方法增强。
比如说,租房代理帮我筛选房子情况,他筛选一套房子发现太旧又太贵,直接pass不用叫我去看房了。我只管看房,代理负责筛选,这就是 “看房方法” 的增强。
当然,一个代理对象可以代理多个委托对象。 这个也可以与方法增强相结合:房子不符合我的要求,但可能符合另一个租房人的要求,那就让他去看房吧。
UML类图
上文使用场景中有这么一句话:“为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口”。很好理解,既然是代理,就是代理对象替委托对象做事情,所以代理对象拥有与委托对象相同的行为,这些共同的行为就是由上面类图中的抽象类或者接口 Subject
规范的。
示例
下面以租房子的租房代理为例实践下代理模式,我们先来看看示例的 UML 图。
UML类图
静态代理
1、House 类就是房子的实体类,提供一些 get 、set 方法。
public class House{
// 房屋名
private String name;
// 价格
private float price;
// 面积
private float area;
public String getName(){
return this.name;
}
public House setName(String name){
this.name = name;
return this;
}
public float getPrice(){
return this.price;
}
public House setPrice(float price){
this.price = price;
return this;
}
public House setArea(float area){
this.area = area;
return this;
}
public float getArea(){
return this.area;
}
}
2、房屋租赁接口 IRent 规定了租房过程中的行为,由委托人和代理人进行实现。
public interface IRent{
// 看房子
public boolean lookHouse(House house);
// 签合同
public void signContract();
}
3、代理类 RentalProxy 是租房代理人,负责对租房方法进行增强,当符合要求时,才会通知租房委托人 MrA 执行实际的租房行为。
/**
* 静态代理 —— 租赁代理
*/
public class RentalProxy implements IRent {
private IRent mRent;
public RentalProxy(IRent mRent) {
this.mRent = mRent;
}
@Override
public boolean lookHouse(House house) {
if (house.getArea() < 50) {
System.out.println("太小了,不符合小A的要求,不用通知小A去看房" + house.getName() + "了");
return false;
}
// 执行代理对象的真实逻辑
return mRent.lookHouse(house);
}
@Override
public void signContract() {
mRent.signContract();
}
}
可以看到,代理人的构造方法中注入了租房接口,实际上注入的是租房委托人对象。
另外,租房代理人预先对房屋面积进行了筛选,只有房屋面积不小于 50 平米,才会通知租房委托人小 A 去看房。
4、MrA 是租房委托人,其执行实际的租房行为。
public class MrA implements IRent {
@Override
public boolean lookHouse(House house) {
// 代理对象执行实际的业务
System.out.println("小A去看了房子" + house.getName());
return true;
}
@Override
public void signContract() {
// 代理对象执行实际的业务
System.out.println("小A签了租赁合同");
}
}
5、main 函数作为调用方,调用了上述代码。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
House houseM = new House();
houseM.setName("M").setArea(40F).setPrice(2500F);
House houseN = new House();
houseN.setName("N").setArea(60F).setPrice(4000F);
/*
* 静态代理
*/
// 代理对象
MrA mrA = new MrA();
// 委托对象
RentalProxy rentalProxy = new RentalProxy(mrA);
if(rentalProxy.lookHouse(houseM)){
rentalProxy.signContract();
};
if(rentalProxy.lookHouse(houseN)){
rentalProxy.signContract();
};
}
}
执行结果如下:
可以看到,房子 M 面积不符合要求,代理人不会通知委托人看房子及签订合同;房子 N 符合要求,会通知委托人看房子及签订合同。
我们已经知道,上面这种是静态代理的实现方式。实际上静态代理与动态代理的区分,正是从编码的角度来说的(还有从适用范围的角度来区分的代理类型,诸如远程代理、虚拟代理、安全代理等,本文不做延伸了)。
说一下 静态代理和动态代理的分别:
静态代理,在代码运行前,就已经存在了代理类的 .class 编译文件;
动态代理,在代码运行时,通知反射来动态地生成代理类的对象,并确定到底来代理谁。也就是我们在编码阶段无须知道代理谁,代理谁将会在代码运行时动态决定。
Java 提供了动态代理的接口 InvocationHandler
,实现该接口需要重写 invoke()
方法。
下面我们还是从代码上来理解动态代理模式。
动态代理
动态代理与静态代理在代码上的区分主要是 代理类,我们看下生成动态代理的类 DynamicPurchasing
。其需要实现接口 InvocationHandler
并实现接口中的方法 invoke()
。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 动态代理的生成类
*/
public class DynamicPurchasing implements InvocationHandler{
private Object object;
public DynamicPurchasing(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 反射得到代理对象
Object result = method.invoke(object, args);
return result;
}
}
在上述代码中,我们声明了一个 Object
的引用,该引用指向委托类,之后通过 invoke()
方法反射得到动态代理对象。
那这个类,我们该怎么用呢?我们来看一下调用方代码。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
House houseM = new House();
houseM.setName("M").setArea(40F).setPrice(2500F);
House houseN = new House();
houseN.setName("N").setArea(60F).setPrice(4000F);
/*
* 动态代理
*/
InvocationHandler invocationHandler = new DynamicPurchasing(mrA);
IRent rent = (IRent) getProxyInstance(mrA.getClass(), invocationHandler);
if (rent != null) {
rent.lookHouse(houseM);
rent.lookHouse(houseN);
}
}
// 生成动态代理
private static Object getProxyInstance(Class clz, InvocationHandler handler) {
return Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), handler);
}
}
上述代码第 17
行,调用 DynamicPurchasing
构造时传入了委托人 MrA 的对象,即我们上面提到的指向委托人的 Object
引用,也就是说,我们要生成 MrA 的代理对象;
上述代码第 18
行,调用 getProxyInstance()
方法生成了代理对象。
getProxyInstance()
中调用的方法是 Java 的 Proxy 类提供的,
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
介绍一下参数:
- ClassLoader loader,委托类的类加载器;
- Class<?>[] interfaces,委托类实现的接口集合,生成的代理类也要实现这些接口;
- InvocationHandler h,即我们的动态代理的生成类
DynamicPurchasing
。
这样,我们就得到了一个动态代理对象,可以通过此对象调用委托人的实际行为了。
那如果要在动态代理中做方法增强,该怎么实现呢?
重点还是在 InvocationHandler
接口中的方法 invoke()
,invoke() 的参数 method
即调用方要调用的委托对象的方法,参数 Object[] args
即此方法入参。我们改写下代码:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 反射得到代理对象
if ("lookHouse".equals(method.getName())) {
House house = (House) args[0];
if (house.getArea() < 50) {
System.out.println("太小了,不符合小A的要求,不用通知小A去看房" + house.getName() + "了");
return null;
}
}
Object result = method.invoke(object, args);
return result;
}
总结
从上面的分析和例子我们可以看出,静态代理类是在编码阶段就确定了的,代理类持有委托类的引用;动态代理类是在代码的执行过程中动态生成的,更灵活,能够实现委托对象和代理对象的解耦。
另外,如果不需要做方法增强,动态代理的生成类 DynamicPurchasing
与业务无关,一个项目甚至可以只存在这么一个动态代理的生成类;当需要在动态代理对方法增强时,就如代码清单 9 所示,动态代理的生成类 DynamicPurchasing
与业务相关,项目中可能会存在多个 DynamicPurchasing
。
本讲的示例源码已经放在 Gihub 上:ProxyPattern
感谢
- 《Android源码设计模式解析与实战》 何红辉 关爱民
- 《Android进阶之光》 刘望舒
- 在线绘图网站 ProcessOn