01.29 面向對象的可複用設計模式之代理模式(13

代理模式(Proxy pattern)就是給某一個對象提供一個代理,並由代理對象控制對原對象的引用。在一些情況下,一個客戶不想或者不能直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。例如電腦桌面的快捷方式就是一個代理對象,快捷方式是它所引用的程序的一個代理。

代理模式根據其目的和實現方式不同可分為很多種類,其中常用的幾種代理模式簡要說明如下:

遠程代理(Remote Proxy):為一個位於不同的地址空間的對象提供一個本地的代理對象,這個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中,遠程代理又稱為大使(Ambassador)。

虛擬代理(Virtual Proxy):如果需要創建一個資源消耗較大的對象,先創建一個消耗相對較小的對象來表示,真實對象只在需要時才會被真正創建。

保護代理(Protect Proxy):控制對一個對象的訪問,可以給不同的用戶提供不同級別的使用權限。

緩衝代理(Cache Proxy):為某一個目標操作的結果提供臨時的存儲空間,以便多個客戶端可以共享這些結果。

智能引用代理(Smart Reference Proxy):當一個對象被引用時,提供一些額外的操作,例如將對象被調用的次數記錄下來等。


面向對象的可複用設計模式之代理模式(13/24)

Subject(抽象主題角色):它聲明瞭真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程。

Proxy(代理主題角色):它包含了對真實主題的引用,從而可以在任何時候操作真實主題對象;在代理主題角色中提供一個與真實主題角色相同的接口,以便在任何時候都可以替代真實主題;代理主題角色還可以控制對真實主題的使用,負責在需要的時候創建和刪除真實主題對象,並對真實主題對象的使用加以約束。通常,在代理主題角色中,客戶端在調用所引用的真實主題操作之前或之後還需要執行其他操作,而不僅僅是單純調用真實主題對象中的操作。

RealSubject(真實主題角色):它定義了代理角色所代表的真實對象,在真實主題角色中實現了真實的業務操作,客戶端可以通過代理主題角色間接調用真實主題角色中定義的操作。

<code>#include <iostream> 

using namespace std;

#define SAFE_DELETE(p) if (p) { delete p; p = NULL;}

class CSubject
{
public:
CSubject(){};
virtual ~CSubject(){}

virtual void Request() = 0;
};

class CRealSubject : public CSubject
{
public:
CRealSubject(){}
~CRealSubject(){}

void Request()
{
cout< }
};

class CProxy : public CSubject
{
public:
CProxy() : m_pRealSubject(NULL){}
~CProxy()
{
SAFE_DELETE(m_pRealSubject);
}

void Request()
{
if (NULL == m_pRealSubject)
{
m_pRealSubject = new CRealSubject();
}
cout< m_pRealSubject->Request();
}

private:
CRealSubject *m_pRealSubject;
};

int main()
{
CSubject *pSubject = new CProxy();
pSubject->Request();

SAFE_DELETE(pSubject);
}/<iostream>/<code>

Output:

<code>CProxy Request            
CRealSubject Request /<code>

智能指針使用引用計數實現時,就是最好的使用代理模式的例子。在下面的例子中,SmartPtr就是一個代理類,而T*m_pData才是實際的數據。SmartPtr代理實際的數據,去實現了指針的行為,添加了引用計數,從而實現了智能指針。

<code>#include <iostream>
#include <windows.h>\t// InterlockedIncrement
using namespace std;

#define SAFE_DELETE(p) if (p) { delete p; p = NULL; }

class KRefCount\t\t\t// 封裝了一個整數及其增1和減1操作的計數器對象
{
public:
KRefCount():m_nCount(0){}

public:
unsigned AddRef()\t// 保證在一個線程訪問變量時其它線程不能訪問
\t{
\t\treturn InterlockedIncrement(&m_nCount);
\t}
unsigned Release()
\t{
\t\treturn InterlockedDecrement(&m_nCount);
\t}
void Reset()
\t{
\t\tm_nCount = 0;
\t}

private:
long m_nCount;

};

template <typename>\t// 模板
class SmartPtr\t\t\t// 封裝了一個指針及計數器對象
{
public:
SmartPtr(void)
: m_pData(NULL)
{
m_pReference = new KRefCount();
m_pReference->AddRef();
\t\tcout< }

SmartPtr(T* pValue)
: m_pData(pValue)
{
m_pReference = new KRefCount();
m_pReference->AddRef();
\t\tcout< }

SmartPtr(const SmartPtr& sp)
: m_pData(sp.m_pData)
, m_pReference(sp.m_pReference)
{
m_pReference->AddRef();
\t\tcout< }

~SmartPtr(void)
{
if (m_pReference && m_pReference->Release() == 0)
{
SAFE_DELETE(m_pData);
SAFE_DELETE(m_pReference);
\t\t\tcout< }
}

\t// 操作符重載
inline T& operator*()
{
return *m_pData;
}

inline T* operator->()
{
return m_pData;
}

SmartPtr& operator=(const SmartPtr& sp)
{
if (this != &sp)
{
if (m_pReference && m_pReference->Release() == 0)
{
SAFE_DELETE(m_pData);
SAFE_DELETE(m_pReference);
}

m_pData = sp.m_pData;
m_pReference = sp.m_pReference;
m_pReference->AddRef();
}

return *this;
}

SmartPtr& operator=(T* pValue)
{
if (m_pReference && m_pReference->Release() == 0)
{
SAFE_DELETE(m_pData);
SAFE_DELETE(m_pReference);
}

m_pData = pValue;
m_pReference = new KRefCount;
m_pReference->AddRef();
return *this;
}

T* Get()\t\t\t\t// 返回智能指針對象的裸指針
{
T* ptr = NULL;
ptr = m_pData;
return ptr;
}

void Attach(T* pObject)// 將裸指針綁定到智能指針對象
{
if (m_pReference->Release() == 0)
{
SAFE_DELETE(m_pData);
SAFE_DELETE(m_pReference);

}

m_pData = pObject;
m_pReference = new KRefCount;
m_pReference->AddRef();
}

T* Detach()// 返回智能指針對象的裸指針交將計數器置0
{
T* ptr = NULL;

if (m_pData)
{
ptr = m_pData;
m_pData = NULL;
m_pReference->Reset();
}
return ptr;
}

private:
KRefCount* m_pReference;\t// 計數器對象
T* m_pData;\t\t\t// 裸指針
};

class CTest\t\t\t\t\t\t// for test
{
public:
CTest(int b) : a(b) {}
int a;
};

int main()
{
\t{\t\t\t\t\t\t\t// local scope for test
\t\tSmartPtr<ctest> pSmartPtr1(new CTest(10));
\t\tSmartPtr<ctest> pSmartPtr2(new CTest(20));

\t\tpSmartPtr1 = pSmartPtr2;
\t\tcout<\t}
\tsystem("pause");
}

/*
Output:
智能指針參數構造!
智能指針參數構造!

40
智能指針析造!
*//<ctest>/<ctest>
/<typename>/<windows.h>/<iostream>/<code>

1 代理模式的應用場景

代理模式的類型較多,不同類型的代理模式有不同的優缺點,它們應用於不同的場合:

1.1 當客戶端對象需要訪問遠程主機中的對象時可以使用遠程代理。

1.2 當需要用一個消耗資源較少的對象來代表一個消耗資源較多的對象,從而降低系統開銷、縮短運行時間時可以使用虛擬代理,例如一個對象需要很長時間才能完成加載時。

1.3 當需要為某一個被頻繁訪問的操作結果提供一個臨時存儲空間,以供多個客戶端共享訪問這些結果時可以使用緩衝代理。通過使用緩衝代理,系統無須在客戶端每一次訪問時都重新執行操作,只需直接從臨時緩衝區獲取操作結果即可。

1.4 當需要控制對一個對象的訪問,為不同用戶提供不同級別的訪問權限時可以使用保護代理。

1.5 當需要為一個對象的訪問(引用)提供一些額外的操作時可以使用智能引用代理。

2 代理模式和裝飾模式的異同

代理模式和裝飾模式的代碼實現方式很相同,主要不同點是代理模式關注與被代理對象行為的控制,然而裝飾模式關注於在一個對象上動態的添加方法。

代理模式可以對客戶端隱藏被代理對象的具體實現,代理模式的時候常常是在一個代理類中創建一個對象的實例,當使用裝飾模式的時候,將原始對象轉為一個參數傳遞給裝飾者的構造器中。

代理模式強調的是限制,裝飾模式強調的是增強

3 代理模式的優缺點

2.1 優點

3.1.1 能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。

3.1.2 客戶端可以針對抽象主題角色進行編程,增加和更換代理類無須修改源代碼,符合開閉原則,系統具有較好的靈活性和可擴展性。

3.2 缺點

3.2.1 由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢,例如保護代理。

3.2.2 實現代理模式需要額外的工作,而且有些代理模式的實現過程較為複雜,例如遠程代理。

4 代理模式和委託

代理:是把一些事情交給某人幫忙去完成。

委託:是當某件事情發生的時候,順便幹某件事情。委託就相當於一個觸發器罷了。

5 總結

代理模式和適配器模式的類適配器很像,再一看,又和裝飾模式非常像;不仔細區分,真的是很容易混亂的。下面就做簡單的區分,說多了也都是“狡辯”了。

適配器Adapter為它所適配的對象提供了一個不同的接口。相反,代理提供了與它的實體相同的接口。然而,用於訪問保護的代理可能會拒絕執行實體會執行的操作,因此,它的接口實際上可能只是實體接口的一個子集。

儘管Decorator的實現部分與代理相似,但Decorator的目的不一樣。Decorator為對象添加一個或多個功能,而代理則控制對對象的訪問。

結構型設計模式之間都有一些細微的差別。你也可以說,在適配器模式進行接口適配時,添加一些數據轉換就變成了遠程代理;你也可以說裝飾模式雖然功能不一樣,但也是大同小異;是的,不管你怎麼說,就像1000 個讀者心中有1000 個哈姆雷特一樣,每個人對設計模式的理解都是不一樣的;最重要的是我們能在實際應用中進行活學活用,如果能做到這個;不管什麼設計模式,那只是一個名字,就像對於富人來說,錢只是一個銀行卡上的一個數字一樣。

-End-


分享到:


相關文章: