🔥 核心
代理模式就是为原对象找一个代理,代理可以保护、缓存、增强、控制原对象。
🙁 问题场景
你是一名 SUPER STAR。
作为一名超级明星,在光鲜亮丽、万众瞩目的同时,你也有着许多烦恼。你每天要与广告商洽谈合作,要联系演出场地进行筹划,还要与粉丝们交流互动…这令你有些力不从心。
SUPER STAR 的烦恼,有谁能明白呢?你心里暗暗叹息道。
🙂 解决方案
聪明的你立即想到了一个好法子:找一个经纪人。
经纪人帮你与赞助商接洽、帮你筛选广告商、帮你安排演出日程表、帮你筹划演出场地、帮你与粉丝们交流互动…并且,在参加各种活动时,你甚至不必亲自到场,而是由你的经纪人代理出席。
你终于可以专注于自己的事情——唱、跳、Rap、篮球 。
🌈 有趣的例子
用一大捆 现金(Cash)
作为支付方式显然非常不方便且不安全,用 银行卡(Card)
对现金进行代理时不错的选择。它们都实现了相同的接口(支付方式(Payment)
),目标类和代理类之间通常是这样的关系。
支付方式接口
interface Payment {
void pay(int money);
void save(int money);
}
现金
class Cash implements Payment {
private int total = 10000;
@Override
public void pay(int money) {
total -= money;
System.out.println("付钱ing");
System.out.println("当前总金额: " + total);
}
@Override
public void save(int money) {
total += money;
System.out.println("存钱ing");
System.out.println("当前总金额: " + total);
}
}
银行卡
class Card implements Payment {
// 对该现金对象进行代理
private Cash cash;
public Card(Cash cash) {
this.cash = cash;
}
@Override
public void pay(int money) {
preCheck();
cash.pay(money);
postCheck();
}
@Override
public void save(int money) {
preCheck();
cash.save(money);
postCheck();
}
private void preCheck() {
System.out.println("现已开启安全保护检查");
}
private void postCheck() {
System.out.println("现已关闭安全保护检查");
}
}
public class ProxyPatternDemo {
public static void main(String[] args) {
// 获得一捆现金
Cash cash = new Cash();
// 用银行卡对现金进行代理
Card card = new Card(cash);
// 用卡付钱
card.pay(3000);
// 用卡存钱
card.save(8000);
}
}
现已开启安全保护检查
付钱ing
当前总金额: 7000
现已关闭安全保护检查
现已开启安全保护检查
存钱ing
当前总金额: 15000
现已关闭安全保护检查
☘️ 使用场景
◾️延迟初始化(虚拟代理)。如果你有一个偶尔使用的重量级服务对象,一直保持该对象运行会消耗系统资源时,可使用代理模式。
你无需在程序启动时就创建该对象,可将对象的初始化延迟到真正有需要的时候。
◾️访问控制(保护代理)。如果你只希望特定客户端使用服务对象,这里的对象可以是操作系统中非常重要的部分,而客户端则是各种已启动的程序(包括恶意程序),此时可使用代理模式。
代理可仅在客户端凭据满足要求时将请求传递给服务对象。
◾️本地执行远程服务(远程代理)。适用于服务对象位于远程服务器上的情形。
在这种情形中,代理通过网络传递客户端请求,负责处理所有与网络相关的复杂细节。
◾️记录日志请求(日志记录代理)。适用于当你需要保存对于服务对象的请求历史记录时。
代理可以在向服务传递请求前进行记录。
◾️缓存请求结果(缓存代理)。适用于需要缓存客户请求结果并对缓存生命周期进行管理时。
特别是当返回结果的体积非常大时。代理可对重复请求所需的相同结果进行缓存,还可使用请求参数作为索引缓存的键值。
◾️智能引用。可在没有客户端使用某个重量级对象时立即销毁该对象。
代理会将所有获取了指向服务对象或其结果的客户端记录在案。代理会时不时地遍历各个客户端,检查它们是否仍在运行。如果相应的客户端列表为空,代理就会销毁该服务对象,释放底层系统资源。
代理还可以记录客户端是否修改了服务对象。其他客户端还可以复用未修改的对象。
🧊 实现方式
(1)如果没有现成的服务接口,你就需要创建一个接口来实现代理和服务对象的可交换性。从服务类中抽取接口并非总是可行的,因为你需要对服务的所有客户端进行修改,让它们使用接口。备选计划是将代理作为服务类的子类,这样代理就能继承服务的所有接口了。
(2)创建代理类,其中必须包含一个存储指向服务的引用的成员变量。通常情况下,代理负责创建服务并对其整个生命周期进行管理。在一些特殊情况下,客户端会通过构造函数将服务传递给代理。
(3)根据需求实现代理方法。在大部分情况下,代理在完成一些任务后应将工作委派给服务对象。
(4)可以考虑新建一个构建方法来判断客户端可获取的是代理还是实际服务。你可以在代理类中创建一个简单的静态方法,也可以创建一个完整的工厂方法。
(5)可以考虑为服务对象实现延迟初始化。
🎲 优缺点
➕ 你可以在客户端毫无察觉的情况下控制服务对象。
➕ 如果客户端对服务对象的生命周期没有特殊要求,你可以对生命周期进行管理。
➕ 即使服务对象还未准备好或不存在,代理也可以正常工作。
➕ 开闭原则。你可以在不对服务或客户端做出修改的情况下创建新代理。
➖ 服务响应可能会延迟。
➖ 代码可能会变得复杂,因为需要新建许多类。
🌸 补充
上面讨论的都是静态代理,更推荐的是动态代理。
动态代理就是在Source源代码阶段,Class类对象阶段,Runtime运行时阶段中的类对象阶段做手脚。