一、介绍
代理模式(Proxy Pattern),属于结构型设计模式。主要目的是为了解决给对象方法进行增强,又不修改原对象方法。
通过代理类对被代理对象进行代理,可以在被代理对象执行方法前后添加附加功能,丝毫不需要修改原方法中的逻辑。
仅从字面意思我们也可以猜到,在代理模式中,从直接访问对象的方式转变为我们通过代理间接访问对象。这就需要我们引入一个代理类,我们只需要访问代理类即可,由代理类访问目标对象。
为其他对象提供一种代理以控制对这个对象的访问。
二、主要角色
从上面的介绍我们得知,在代理模式中,存在的基本角色有:代理类、被代理类、抽象接口。下面我们对这几个角色进行阐述:
抽象接口(BarInterface)
按照面向接口编程思想,我们首先需要创建一个抽象接口来定义其实现类的行为。
被代理类(BarImpl)
实现抽象接口(BarInterface),对接口中定义的行为进行具体实现。作为被代理类,它的方法实现为该方法的核心逻辑,当其被代理类代理增强后,该实现类方法的逻辑将被增强。
代理类(BarProxy)
客户端调用的实际类型。在代理模式中,客户端使用的都是被代理类所代理的对象,该对象在原始对象的基础上,对原对象的方法进行增强,添加了一些附加逻辑。
下面是代理模式通用的UML类图
1. 代码演示
根据上面通用UML类图,我们使用代码对其进行实现,通过代码的形式对代理模式有个初步了解。
抽象接口(BarInterface)
新建接口类
BarInterface
public interface BarInterface { void doSomething_1(); void doSomething_2(); void doSomething_3(); }
被代理类(BarImpl)
新建抽象接口类
BarInterface
的实现类BarImpl
,并对接口类所定义的行为进行具体实现。public class BarImpl implements BarInterface{ @Override public void doSomething_1() { System.out.println("被代理对象执行方法:doSomething_1()"); } @Override public void doSomething_2() { System.out.println("被代理对象执行方法:doSomething_1()"); } @Override public void doSomething_3() { System.out.println("被代理对象执行方法:doSomething_1()"); } }
代理类(BarProxy)
新建代理类
BarProxy
,该类与被代理类BarImpl
一样,需要实现抽象接口类BarInterface
,并对接口定义的方法进行实现。此时我们发现被代理类BarImpl
和代理类BarProxy
似乎没什么区别,都实现了相同的接口,因此我们需要从功能上对这两个类进行区别,代理类的功能是对被代理类进行方法增强,因此在代理类中,我们需要通过构造方法初始化一个被代理类对象的字段,然后在代理类的方法内部对被代理进行方法增强即可。具体代码如下所示public class BarProxy implements BarInterface{ private final BarInterface bar; public BarProxy(BarInterface bar) { this.bar = bar; } @Override public void doSomething_1() { System.out.println("被代理对象doSomething_1()方法执行前...."); bar.doSomething_1(); System.out.println("被代理对象doSomething_1()方法执行后...."); } @Override public void doSomething_2() { System.out.println("被代理对象doSomething_2()方法执行前...."); bar.doSomething_2(); System.out.println("被代理对象doSomething_2()方法执行后...."); } @Override public void doSomething_3() { System.out.println("被代理对象doSomething_3()方法执行前...."); bar.doSomething_3(); System.out.println("被代理对象doSomething_3()方法执行后...."); } }
客户端类(ProxyClient)
新建客户端类
ProxyClient
,在main()
方法中对医生代码进行演示。public class ProxyClient { public static void main(String[] args) { System.out.println("bar对象未被代理时"); BarInterface bar = new BarImpl(); bar.doSomething_1(); bar.doSomething_2(); bar.doSomething_3(); System.out.println(); System.out.println("bar对象被代理类BarProxy所代理,方法被代理类增强"); BarInterface barProxy = new BarProxy(bar); barProxy.doSomething_1(); barProxy.doSomething_2(); barProxy.doSomething_3(); } }
执行该方法后,得到以下输出,从输出中,我们发现,将对象进行代理后,其方法被代理类所增强,而且是通过代理类增强,而非是修改原对象中方法的逻辑。
同时也告诉我们,对象被代理后,其实际类型为该对象对应的代理类型,而不再是原类型。
三、案例
生活中像代理模式的案例到处都是,以租房为例,我们在租房时,往往是直奔租房中介(比如链家、我爱我家)等,中介房源多呀,通过它们给我们找房看房乱七八糟的,我们就可以租到我们心仪的房子了。但这些房子是它们中介的吗?并不是,房子有它自己的主人,只是中介和他们之间有合作,房东平时也挺忙的,没空天天等着我们看房,于是就让中介作为他们的代理人,我们只需要和中介交流就可以了,如果我们愿意租房,中介再和房东之间谈价格。
另外,当我们住进房屋后,如果有电器什么的坏了需要维修,并不是房东直接过来给我们修,房东哪有这个闲工夫,依然是中介作为房东的代理人,过来给我们维修东西。
下面我们就以租房这个事情为例,通过代码进行演示
1. 租房接口类RentHouse
新建租房接口类RentHouse
,我们假设租房分为四个流程:找房、交钱、入住、维修。于是我们在该接口类中定义这些功能。
public interface RentHouse {
// 找房子
void findHouse();
// 交钱
void pay();
// 入住
void live();
// 维修
void maintain();
}
2. 租客类Renter
新建租客类Renter
,并实现租房接口类RentHouse
中定义的方法。
public class Renter implements RentHouse{
@Override
public void findHouse() {
System.out.println("租客找房子...");
}
@Override
public void pay() {
System.out.println("租客支付房屋租金...");
}
@Override
public void live() {
System.out.println("租客拎包入住...");
}
@Override
public void maintain() {
System.out.println("维修家电家具...");
}
}
3. 租房中介代理类RentProxy
新建租房中介代理类RentProxy
,同样实现租房接口类RentHouse
中定义的方法,中介和租客差不多,都是来租他的房子的,但对于房东来说,中介是租客的代理人,是代表租客来找他租房的,因此在中介代理类RentProxy
中需要维护一个租客类作为代理对象。
public class RentProxy implements RentHouse {
private final RentHouse rentHouse;
public RentProxy(RentHouse rentHouse) {
this.rentHouse = rentHouse;
}
@Override
public void findHouse() {
System.out.println("中介扩大找房范围");
rentHouse.findHouse();
}
@Override
public void pay() {
System.out.println("中介把房屋租金谈下来了,便宜500块");
rentHouse.pay();
}
@Override
public void live() {
System.out.println("中介提供免费搬家服务");
rentHouse.live();
}
@Override
public void maintain() {
System.out.println("中介找来了维修师傅");
rentHouse.live();
}
}
4. 客户端类
新建客户端类RentClient
租客没有通过中介找房
public class RentClient { public static void main(String[] args) { RentHouse rentHouse = new Renter(); System.out.println("租客直接找房,没有通过中介"); rentHouse.findHouse(); rentHouse.pay(); rentHouse.live(); rentHouse.maintain(); } }
此时输出结果如下
租客通过中介找房
此时我们找房过程的实际类型是中介,代码如下所示
public class RentClient { public static void main(String[] args) { RentHouse rentHouse = new Renter(); System.out.println("租客通过中介找房子"); System.out.println("---------"); RentHouse rentProxy = new RentProxy(rentHouse); rentProxy.findHouse(); System.out.println(); rentProxy.pay(); System.out.println(); rentProxy.live(); System.out.println(); rentProxy.maintain(); } }
此时输出结果如下
在此案例中我们可以发现,通过代理模式,我们可以在不改动既有逻辑的情况下,对其进行增强。
四、应用
代理模式的应用场景十分广泛,如
日志记录
我们尝尝在写一个方法时,该方法内仅仅实现其主要逻辑,当我们需要对该方法的调用进行日志记录时,可以通过代理模式对该方法进行增强,在代理类中对该方法进行日志记录。
如果将日志记录也写在该方法内,则会导致该方法的指责不够清晰且十分臃肿,违背单一职责的设计原则。
权限校验
有些方法仅允许具有特定权限的用户调用,而没有该权限的用户在调用该方法时要么抛异常。要么直接返回,从而跳过该方法的执行。此时可以将对用户权限的校验交给代理类实现。
如果将权限校验也写在该方法内,则会导致该方法的指责不够清晰且十分臃肿,违背单一职责的设计原则。
远程代理
翻墙的原理就是通过对我们的ip进行代理,从而实现科学上网。
等等....
五、优缺点
优点:
- 对扩展开放,对修改关闭
缺点:
- 引入代理类,增加系统复杂度
六、与适配器模式的区别
相同点:
- 两者都引入了一个第三方类(适配器模式引入适配器类,代理模式引入代理类)。且第三方类中都维护了一个被适配对象(或被代理对象)作为内部属性,通过调用适配器的方法(或代理类的方法),在内部对被适配对象(或被代理对象)进行调用。
不同点:
- 适配器模式的目的是通过适配器类,将A接口转换为B接口;而代理模式中不转换接口,只对接口中的方法进行增强。
七、与装饰器模式的区别
相同点:
- 两者都引入了一个第三方类(装饰器模式引入装饰器类,代理模式引入代理类)。且第三方类中都维护了一个被装饰对象(或被代理对象)作为内部属性,通过调用装饰器的方法(或代理类的方法),在内部对被装饰对象(或被代理对象)进行调用。
- 从简化代码来说,装饰器模式和代理模式都是对已有对象进行方法增强。
不同点:
- 装饰器增强的功能常常深刻影响着被装饰对象,即被装饰的对象会面临被修改的可能;而代理模式中增强的功能与被代理对象没有任何关系,即增强的功能不会对被代理对象产生任何影响。
- 装饰器模式中,多个装饰器可以通过任何组合方式对被装饰对象进行嵌套装饰,这是一种动态的增强行为;而代理模式中,代理类对被代理对象的增强是由代码设计决定的,一旦在设计阶段对某一对象进行代理,那么在整个程序运行期间不可改变,这是一种静态的增强行为。
- 装饰器模式中,装饰器可以仅针对被装饰对象的某一个方法进行增强;而代理模式中,代理类针对被代理对象的所有方法进行增强。
纸上得来终觉浅,绝知此事要躬行。
————————我是万万岁,我们下期再见————————