C++ 設計模式 - 代理模式 Proxy Pattern

在日常生活中,我們常常遇到這樣的情況:你想要完成某件事情,但由於種種原因,你無法直接接觸到那個目標,於是你請來了一個「代理」來幫你處理一切。例如你需要買一台很難找的限量版遊戲機,但由於時間或距離問題,你沒辦法親自去購買,所以你委託了一個代購服務。而這個代購就是你的「代理」。在軟體設計中,代理模式 Proxy Pattern 就是利用類似的概念,讓一個物件(代理)代表另一個物件來控制對它的存取。聽起來有點抽象?別擔心,我們馬上進入正題。

什麼是代理模式?

代理模式是一種結構型設計模式,代理模式的核心思想是:為某個物件提供一個替代者,以控制對這個物件的存取。這樣做的好處是你可以在不改變原物件的情況下,新增一些額外的功能,或是對存取進行控制。代理模式可以被看作是一個中間層,這個中間層負責把客戶端的請求轉發給真實物件,並在過程中執行一些額外的邏輯。

假如你是一位繁忙的企業高階經理,每天都有無數的會議和決策要處理。這時候一個得力的助理就顯得重要。助理可以幫你過濾不重要的事物,安排你的行程,甚至代表你參加一些會議。這個助理就是現實生活中的「代理」。

簡單來說,代理模式中有三個角色:

  1. Subject:定義了客戶端可以呼叫的方法。
  2. RealSubject:實際執行請求的真實物件。
  3. Proxy:代理物件,負責控制對 RealSubject 的存取。

代理模式在網頁載入中的應用

讓我們以網頁載入的例子來解釋代理模式的實際應用。假設我們有一個網站,其中包含許多高解析度的圖片,這些圖片可能會因為其大容量而導致網頁載入緩慢。為了優化使用者體驗,我們可以使用代理模式,先顯示一個縮圖,等使用者真的需要查看大圖時,再去載入原圖。

首先我們定義一個 Image 介面,裡面有一個方法 display,用來顯示圖片,

1
2
3
4
5
6
// 抽象介面 Subject
class Image {
public:
virtual void display() = 0;
virtual ~Image() = default;
};

接下來我們實現這個介面來載入並顯示實際的圖片,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 實際主題 RealSubject
class RealImage : public Image {
private:
std::string filename;

void loadFromDisk() {
std::cout << "Loading " << filename << " from disk\n";
}

public:
RealImage(const std::string& filename) : filename(filename) {
loadFromDisk();
}

void display() override {
std::cout << "Displaying " << filename << std::endl;
}
};

最後我們實現一個 ProxyImage 來控制對 RealImage 的存取,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 代理物件 Proxy
class ProxyImage : public Image {
private:
std::string filename;
std::unique_ptr<RealImage> realImage;

public:
ProxyImage(const std::string& filename) : filename(filename), realImage(nullptr) {}

void display() override {
if (!realImage) {
// lazy initialization
realImage = std::make_unique<RealImage>(filename);
}
realImage->display();
}
};

在客戶端只需透過代理物件來存取圖片,而不必直接接觸到大圖片本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 客戶端程式碼
int main() {
std::unique_ptr<Image> image1 = std::make_unique<ProxyImage>("photo1.jpg");
std::unique_ptr<Image> image2 = std::make_unique<ProxyImage>("photo2.jpg");

// 圖像將從磁碟載入
image1->display();

// 圖像不會再次從磁碟載入,直接顯示
image1->display();

// 圖像將從磁碟載入
image2->display();

return 0;
}

在這個例子中,第一次呼叫 display() 方法時,代理會建立並載入真實圖片。當第二次呼叫時,圖片已經被載入過,因此不會再次載入,直接顯示圖片內容。這樣的處理方式避免了不必要的效能消耗,還提升了效率。

代理模式的優缺點

代理模式帶來了許多好處,最顯而易見的是它能讓我們有效地控制對目標物件的存取。這表示我們可以根據需求延遲載入資源、設定存取權限,甚至在某些情況下,讓一個代理物件負責管理多個請求,進而優化系統的效能。另外代理模式提供了一種優雅的方式,在不改變原有類別的基礎上新增額外功能,這讓我們的程式設計更加靈活,能更好地應對未來的變化。

代理模式也不是沒有缺點,導入代理會在客戶端和真實物件之間增加一個間接層,這可能會導致系統變得更加複雜。如果代理類別中包含了太多額外的邏輯,可能會影響效能。

總結

代理模式是一個實用且靈活的設計模式,適合用於需要控制存取或增加功能的場景中。它的應用非常廣泛,例如虛擬代理、保護代理、智慧代理等,每一種代理模式都有其獨特的價值。但我們也要注意避免因為濫用代理模式而導致系統過度複雜。

就像現實生活中的經紀人或助理一樣,代理為我們處理了許多複雜的細節,讓我們能夠專注於更重要的事務。下次當你面臨需要控制對對象的存取,或者需要在存取前後添加額外邏輯的情況時,不妨考慮使用代理模式。它可能正是你所需要的解決方案。