代理模式 (Proxy Pattern) 是一种结构性设计模式。通过代理模式,我们可以不免直接访问目标对象,转而通过代理对象来访问目标对象。同时,我们可以通过代理对象来控制访问权限以及增强功能。
让我们用 3 个例子来解释清楚什么是代理模式,以及代理模式所带来的好处。
例子1:代购
代购是讲解代理模式中最常见的例子,那么我们也不能免俗地要来讲讲这个例子。
假设我想要购买 A 品牌的洋奶粉,需要通过代购才能够买到这款羊奶粉,那么我们可以这样说:
-
抽象主题角色 (Subject)
买家 -
真实主题角色 (Real Subject)
我 -
代理主题角色 (Proxy)
代购
通过代码来表示:
// 买家
abstract class Buyer {
abstract void buy(String item);
}
// 我
public class Me extends Buyer {
public void buy(String item) {
System.out.println("购买商品: " + item);
}
}
// 代购
class BuyingAgent extends Buyer {
// 代替我买
private Buyer me = new Me();
public void buy(String item) {
System.out.println("我是代购,我正在加拿大温哥华商场");
me.buy(item);
System.out.println("购买完毕,准备快递寄回委托人手里");
}
}
运行程序
public static void main(String[] args) {
Buyer buyingAgent = new BuyingAgent();
buyingAgent.buy("A品牌奶粉");
}
运行之后得到的输出结果是
我是代购,我正在加拿大温哥华商场
购买商品: A品牌奶粉
购买完毕,准备快递寄回委托人手里
这个例子体现出了代理模式的基本用法。我们会在代理类中包含一个真实对象,并在调用真实对象方法之前或之后,我们还可以调用其他函数或者是运行其他逻辑代码。
例子2:房屋中介
假设Kevin有一套房子需要出售,然而他并不懂得市场行情,也没有时间与买家周旋,于是Kevin请了一位房屋中介帮忙销售这套房子。
房屋中介除了可以帮Kevin卖出房屋之外,还可以直接拒绝心理价位以下的出价,以及帮助Kevin处理后续的交易文件以及手续,我们可以将这理解为功能增强。
在这里,房屋中介的职责就是一个代理人,阻止了买家直接与卖家联系,任何事务都必须通过房屋中介处理。
我们可以这样定义:
-
抽象主题角色 (Subject)
卖家 -
真实主题角色 (Real Subject)
卖家 Kevin -
代理主题角色 (Proxy)
房屋中介
通过代码来表示:
abstract class Seller {
abstract void sell(String house, int price);
}
class Kevin extends Seller {
public void sell(String house, int price) {
System.out.println("售出房产: " + house + ", 售价: " + price + "万");
}
}
class Agent extends Seller {
private Seller seller = new Kevin();
// 卖家可接受最低价格
private int acceptablePrice = 100;
public void sell(String house, int price) {
// 控制访问权限,出价过低不出售
if (!isPriceAcceptable(price)) {
System.out.println("抱歉," + price + "万出价过低。");
return;
}
System.out.println("接受出价" + price + "万");
seller.sell(house, price);
processDocuments();
}
private boolean isPriceAcceptable(int price) {
return price >= acceptablePrice;
}
// 功能增强:帮卖家处理交易手续
private void processDocuments() {
System.out.println("处理交易手续");
}
}
运行程序
public static void main(String[] args) {
Seller agent = new Agent();
agent.sell("A套房产", 90);
agent.sell("A套房产", 100);
}
得到输出结果
抱歉,90万出价过低。
接受出价100万
售出房产: A套房产, 售价: 100万
处理交易手续
在这个例子中,我们看到了代理类可以带来访问权限的控制与其他增强功能。
- 访问权限控制
报价过低无法访问真实对象 - 功能增强
处理交易手续
例子3:实际编程工作中的应用场景
假设我们在一家软件公司里工作,当前正在开发一款论坛产品。A组的开发人员已经开发好了发布帖子的接口了,而我们的任务则是给这个发布帖子的接口加上发帖权限检查,以及发帖日志记录的功能。
然而这里有个限制,由于这个接口是由A组开发的,因此他们不希望我们动他们的源代码,这在软件公司里是很常见的事情,不同组之间尽量不去动对方的代码,那么我们需要怎么做,才能在不动到对方代码的情况下,实现我们的功能呢?
答案就是:代理模式
先让我们来看看A组开发的接口:
public interface PostService {
void makePost(String content, int userId);
}
public class PostServiceImpl implements PostService{
public void makePost(String content, int userId) {
// 发布帖子的逻辑
System.out.println("用户" + userId + "发布帖子成功");
}
}
那么我们可以使用代理模式,创建一个代理类,通过代理类我们会在调用真实对象发布帖子的函数之前先进行权限检查,并在调用真实对象发布帖子的函数之后再调用打日志的函数。
public class PostServiceProxy implements PostService {
private PostService postService = new PostServiceImpl();
public void makePost(String content, int userId) {
// 先进性权限检查
validateAuthentication(userId);
// 再实际调用发帖函数
postService.makePost(content, userId);
// 之后打上日志
logActivity("发布帖子", userId);
}
private void validateAuthentication(int userId) {
System.out.println("检查用户" + userId + "权限");
}
private void logActivity(String activity, int userId) {
System.out.print("记录用户" + userId + "活动: " + activity);
}
}
实际运行程序:
public static void main(String[] args) {
PostService postService = new PostServiceProxy();
int userId = 1;
postService.makePost("新人报道帖!", userId);
}
运行结果:
检查用户1权限
用户1发布帖子成功
记录用户1活动: 发布帖子
可以看到,从调用者的角度来看,除了创建的对象是代理类之外,实际用法与真实对象无异,却又能够增加权限检查与打日志两项功能,这便是代理模式的厉害之处。