代理模式 (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活動: 發佈帖子
可以看到,從調用者的角度來看,除了創建的對象是代理類之外,實際用法與真實對象無異,卻又能夠增加權限檢查與打日誌兩項功能,這便是代理模式的厲害之處。