1. 程式人生 > >第14章 結構型模式—代理模式

第14章 結構型模式—代理模式

1. 代理模式(Proxy Pattern)的定義

(1)為其他物件提供一種代理以控制對這個物件的訪問

  ①代理模式在客戶和被客戶訪問的物件之間,引入了一定程度的間接性,客戶是直接使用代理,讓代理來與被訪問的物件進行互動。

  ②這種附加的間接性增加了靈活性和不同的用途

(2)代理模式的結構和說明

  ①Proxy:代理物件,通常實現與具體的目標物件一樣的介面,這樣就可以使用代理來代替具體的目標物件。

  A、儲存一個指向具體目標物件的指標,可以在需要的時候呼叫具體的目標物件,若RealSubject和Subject的介面相同,Proxy會引用Subject,並控制對具體目標物件的訪問

  B、可以為代理類傳入一個RealSubject的引用。也可以在Proxy內部自己建立一個RealSubject,並可能負責建立和刪除它

  C、從Subject繼承,這樣代理就可以用來替代目標物件

  ②Subject:目標介面,定義代理和具體目標物件的介面,這樣可以在任何使用具體目標物件的地方使用代理物件,讓客戶端透明地使用代理。

  ③RealSubject:具體的目標物件,真正實現目標介面要求的功能。

(3)思考代理模式

  ①代理模式的本質控制物件訪問,通過代理目標物件,把代理物件插入到客戶和目標物件之間,從而為客戶和目標物件引入一定的間接性。正是這個間接性,給代理物件很多活動空間。代理物件可以在呼叫具體的目標物件前後,附加很多操作,從而實現新的功能或擴充套件功能。更狠的是,代理物件還可以不去建立和呼叫目標物件

,也就是說,目標物件被完全代理掉了(替換)

  ②代理模式的動機:在面向物件系統中,有些物件由於某種原因(比如物件建立的開銷很大,或者某些操作需要安全控制,或者需要程序外的訪問等),直接訪問會給使用者或系統結構帶來很多麻煩。這時可以增加一個代理物件,以不失透明操作物件的同理來管理/控制對物件的訪問。

  ③代理模式的實現:主要使用物件的組合和委託。但也可以採用物件繼承的方式來實現代理。

【程式設計實驗】聯絡代理(而不是歌星本人)開演唱會

 

#include <iostream>

using namespace std;

//結構型模式:代理模式
//場景:聯絡代理(而不是歌星本人)開演唱會

class Star
{
public:
    virtual void confer() = 0;        //面談
    virtual void signContract() = 0;  //籤合同
    virtual void bookTicket() = 0;    //訂票
    virtual void sing() = 0;          //唱歌
    virtual void collectMoney() = 0;   //收錢
};

//RealStar
class RealStar : public Star
{
public:
    void confer()
    {
        cout << "RealStart.confer()" << endl;
    }
    void signContract()
    {
        cout << "RealStart.signContract()" << endl;
    }
    void bookTicket()
    {
        cout << "RealStart.bookTicket()" << endl;
    }
    void sing()
    {
        cout << "RealStart(ZhouJieLun).sing(()" << endl;
    }
    void collectMoney()
    {
        cout << "RealStart.collectMoney()" << endl;
    }
};

//ProxyStar
class ProxyStar : public Star
{
private:
    Star* realStar;
public:
    ProxyStar(Star* realStar){this->realStar = realStar;}
    void confer()
    {
        cout << "ProxyStar.confer()" << endl;
    }
    void signContract()
    {
        cout << "ProxyStar.signContract()" << endl;
    }
    void bookTicket()
    {
        cout << "ProxyStar.bookTicket()" << endl;
    }
    void sing()
    {
        realStar->sing();
    }
    void collectMoney()
    {
        cout << "ProxyStar.collectMoney()" << endl;
    }
};

int main()
{
    Star* realStar = new RealStar();
    Star* proxyStar = new ProxyStar(realStar);

    //面談、籤合同、訂票、收錢由代理來做
    //實際唱歌時,由代理去找真實明星來唱,對於客戶端不需要
    //直接和真實明白打交道。
    proxyStar->confer();
    proxyStar->signContract();
    proxyStar->bookTicket();
    proxyStar->sing();      //唱歌時由代理去找真實明星來唱
    proxyStar->collectMoney();

    delete realStar;
    delete proxyStar;
    return 0;
}

2. 代理模式的分類

(1)虛代理可以根據需要來建立“大”物件,只有到必須建立物件的時候,虛代理才會去建立物件,從而大大加快程式執行速度,並節省資源。通過虛代理可以對系統進行優化。如開發一個大文件檢視軟體(如圖片100MB),在開啟檔案前,先顯示縮圖,而不是將所有圖片都顯示出來,在需要檢視圖片時,再用Proxy來進行大圖片的開啟)

(2)保護代理:可以在訪問一個物件的前後,執行很多附加的操作,除了進行許可權控制之外,還可以進行很多跟業務相關的處理,而不需要修改被代理的物件。也就是說,可以通過代理來給目標物件增加功能。

(3)Copy-on-write代理:也是虛代理的一個分支,在客戶端操作的時候,只有物件確實改變了,才會真的拷貝(或克隆)一個目標的物件。在實現Copy-on-write時必須對目標物件進行引用計數。代理僅會增加引用。只有當用戶請求一個修改目標物件的操作時,代理才會真正的拷貝它。在這種情況下,代理還必須減少目標物件的引用次數。當引用計數為0時,將目標物件刪除。

(4)遠端代理:隱藏了一個物件存在於不同地址空間的事實,也即是客戶通過遠端代理去訪問一個物件,根本就不關心這個物件在哪裡,也不關心如何通過網路去訪問到這個物件,從客戶的角度來看,它只是在使用代理物件而己。

(5)智慧指標:和保護代理類似,也是允許在訪問一個物件的前後,執行很多附加的操作,這樣一來就可以做很多額外的事情,比如引用計數、第1次引用一個持久物件時,將它載入記憶體、在訪問一個實際物件前,檢查是否己經鎖定了它,以確保其他物件不能改變它等。

(6)Cache代理:為那些昂貴操作的結果提供臨時的儲存空間,以便多個客戶端可以共享這些結構。

(7)防火牆代理:保護物件不被惡意使用者訪問或操作。

(8)同步代理:使多個使用者能夠同時訪問目標物件而沒有衝突。

3. 代理的優點

(1)職責清晰:RealSubject就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過後期的代理完成其他事務,附帶的結果就是程式設計簡潔清晰。

(2)可擴充套件性好:RealSubject可以隨時變化,只要它實現了介面,甭管如何變化,代理可以在完全不做任何修改的情況下使用。

4. 代理的使用場景

(1)需要為一個物件在不同的地址空間提供區域性代表的時候,可以使用遠端代理

(2)需要按照需要建立開銷很大的物件的時候,可以使用虛代理

(3)需要控制對原始物件的訪問的時候,可以使用保護代理

(4)需要在訪問物件執行一些附加操作時,可以使用智慧指引代理。

【程式設計實驗】利用虛代理實現大物件的延時載入

/*
//結構型模式:代理模式
//場景:一次性訪問多條資料問題。
//需求:一個HR(人力資源)應用專案中,當用戶選擇一個部門時要把這個部門下所有員工都顯示出來(只顯示部門ID和姓名)
        在需要的時候,可以選擇檢視某位員工的詳細資訊
//思路:由於訪問多條使用者資料時,基本上只需要看到使用者的姓名可以考慮剛開始從資料庫查詢返回的使用者資料只有編號和使用者姓名,當客戶想要詳細檢視某個使用者資料時,再根據編號從數
        據庫中獲取完整的使用者資料。這樣可以大大減少對記憶體的開銷
//解決方案:使用代理(虛代理),剛開始只用一個不完整的使用者物件(代理物件)。當需要訪問時,再由代理物件從資料庫中重新獲取相應的資料,而這個過程對使用者來說是透明的。
*/

#include <iostream>
#include <string>
#include <vector>

using namespace std;

//////////////////////////////////////////////////////////////////////////
// 字串分割
//
// -------------------------------------------------------------------------
// 函式     : Split
// 功能     : 分割STL標準字串
// 返回值   : void
// 引數     : Container<std::basic_string<CharT> >& v 存放分割結果
// 引數     : const std::basic_string<CharT>& s 待分割字串
// 引數     : const std::basic_string<CharT>& c 分割字串
// -------------------------------------------------------------------------
template<typename CharT, template<typename S, typename Q = std::allocator<S> > class Container>
void Split(Container<std::basic_string<CharT> >& v, const std::basic_string<CharT>& s, const std::basic_string<CharT>& c);

template<template<typename S, typename Q = std::allocator<S> > class Container>
void Split(Container<std::basic_string<char> >& v, const std::basic_string<char>& s, const std::basic_string<char>& c)
{
    if (0 == c.length())
        return;

    std::basic_string<char>::size_type pos1 = 0;
    std::basic_string<char>::size_type pos2 = 0;

    pos1 = 0;
    pos2 = s.find(c);
    while (std::basic_string<char>::npos != pos2)
    {
        v.push_back(s.substr(pos1, pos2 - pos1));

        pos1 = pos2 + c.size();
        pos2 = s.find(c, pos1);
    }

    if (pos1 != s.length())
    {
        v.push_back(s.substr(pos1));
    }
}

//******************************輔助類***************************
//測試資料(在記憶體中模擬資料庫中的值)
class TestDB
{
private:
   TestDB(){}
public:
    static vector<string> userDB; //人員列表
    //static vector<string> depDB;  //部門列表
};

//人員列表初始資料
static vector<string>::value_type initUsers[] =
{
    vector<string>::value_type("use0001,ZhangSan1,010101,male"),
    vector<string>::value_type("use0002,ZhangSan2,010101,male"),
    vector<string>::value_type("use0003,ZhangSan3,010102,male"),
    vector<string>::value_type("use0004,ZhangSan4,010201,male"),
    vector<string>::value_type("use0005,ZhangSan5,010201,male"),
    vector<string>::value_type("use0006,ZhangSan6,010202,male")

};
vector<string> TestDB::userDB(initUsers, initUsers + 6);

//部門列表初始資料
//編號規則:如010201:表示總公司下的二分公司裡的開發部
//static vector<string>::value_type initDepsDB[] =
//{
//    vector<string>::value_type("01,Company"),         //總公司
//    vector<string>::value_type("0101,BranchCompany1"),//一分公司
//    vector<string>::value_type("0102,BranchCompany2"),//二分公司
//    vector<string>::value_type("010101,DevelopDep"),  //開發部
//    vector<string>::value_type("010102,TestDep"),     //測試部
//    vector<string>::value_type("010201,DevelopDep"),  //開發部
//    vector<string>::value_type("010201,serviceDep")   //客服部
//
//};
//vector<string> TestDB::depDB(initDepsDB, initDepsDB + 7);

//**************************************代理類****************************
//定義使用者資料物件的介面
class UserModelApi
{
public:
    virtual string& getUserId() = 0;
    virtual void setUserId(string userId) = 0;
    virtual string&  getName() = 0;
    virtual void setName(string name) = 0;
    virtual string&  getDepId() = 0;
    virtual void setDepId(string name) = 0;
    virtual string&  getSex() = 0;
    virtual void setSex(string sex) = 0;
    virtual ~UserModelApi(){};
};

//真實的目標物件——描述使用者資料的物件
class UserModel: public UserModelApi
{
private:
    string userId; //使用者編號
    string name;   //使用者姓名
    string depId;  //部門編號
    string sex;    //使用者性別
public:

    string& getUserId(){return userId;}
    void setUserId(string userId)
    {
        this->userId = userId;
    }

    string& getName(){return name;}
    void setName(string name)
    {
        this->name = name;
    }

    string& getDepId(){return depId;}
    void setDepId(string depId)
    {
        this->depId = depId;
    }

    string& getSex(){return sex;}
    void setSex(string sex)
    {
        this->sex = sex;
    }
};

//代理物件,代理使用者資料物件
class Proxy : public UserModelApi
{
private:
    UserModel* realSubject; //持有被代理的具體的目標物件
    bool loaded;   //用來標識是否己經重新裝載過資料了

    //重新詢資料庫以獲取完整的使用者資料
    void reload()
    {
        cout << "reload full information,userId ==" <<
             realSubject->getUserId() << endl;

        //實際中,這裡應該處理連線資料庫的程式碼

        //這裡獲取除了userId和name外的資料
        vector<string>::iterator iter = TestDB::userDB.begin();
        vector<string> tmpStr;
        while (iter != TestDB::userDB.end())
        {
            tmpStr.clear();
            Split(tmpStr,*iter,",");

            if(tmpStr.size() == 4)
            {
                if(tmpStr[0] == realSubject->getUserId())
                {
                    realSubject->setDepId(tmpStr[2]);
                    realSubject->setSex(tmpStr[3]);
                }
            }
            ++iter;
        }
    }

public:
    Proxy():loaded(false)
    {
        realSubject = new UserModel();
    }

    ~Proxy(){delete realSubject;}

    string& getUserId()
    {
        return realSubject->getUserId();
    }

    void setUserId(string userId)
    {
        realSubject->setUserId(userId);
    }

    string& getName()
    {
       return  realSubject->getName();
    }

    void setName(string name)
    {
        realSubject->setName(name);
    }

    string& getDepId()
    {
        if(!loaded)
        {
            //從資料庫中重新裝載
            reload();
            //設定重新裝載標誌為true
            loaded = true;
        }
       return  realSubject->getDepId();
    }

    void setDepId(string depId)
    {
        realSubject->setDepId(depId);
    }

    string& getSex()
    {
        if(!loaded)
        {
            //從資料庫中重新裝載
            reload();
            //設定重新裝載標誌為true
            loaded = true;
        }
        return realSubject->getSex();
    }

    void setSex(string sex)
    {
        realSubject->setSex(sex);
    }

    string toString()
    {
        return "userId = " + getUserId() + ","  +
               "name = "   + getName()   + ", " +
               "depId = "  + getDepId()  + ", " +
               "sex = "    + getSex();
    }
};

//實現示例要求的功能
class UserManager
{
private:
    vector<UserModelApi*>  users;

    //清除
    void clearUsers()
    {
        vector<UserModelApi*>::iterator iter = users.begin();
        while(iter != users.end())
        {
            delete *iter;
        }
        users.clear();
    }
public:
    //根據部門編號來獲取該部門下的所有人員
    vector<UserModelApi*> getUserByDepId(string depId)
    {
        //只查詢userId和name兩個值就可以了
        clearUsers();

        //實際中,這裡須訪問資料庫
        vector<string>::iterator iter = TestDB::userDB.begin();
        vector<string> tmpStr;
        while (iter != TestDB::userDB.end())
        {
            tmpStr.clear();
            Split(tmpStr,*iter,",");

            if(tmpStr.size() == 4)
            {
                if(tmpStr[2].substr(0,4) == depId)
                {
                    //這裡建立代理物件,而不是直接建立UserModel
                    Proxy* proxy = new Proxy();

                    //只設置userId和name兩個值就可以了
                    proxy->setUserId(tmpStr[0]);
                    proxy->setName(tmpStr[1]);
                    users.push_back(proxy);
                }
            }
            ++iter;
        }
        return users;
    }

    ~UserManager()
    {
        clearUsers();
    }
};

int main()
{
    UserManager userManager;
    vector<UserModelApi*> Users = userManager.getUserByDepId("0101");

    //如果只是顯示使用者名稱稱,則不需要重新查詢資料庫
    vector<UserModelApi*>::iterator iter = Users.begin();
    while(iter !=Users.end())
    {
        cout <<"userId:=" << (*iter)->getUserId() <<" userName:=" <<
             (*iter)->getName() << endl;
        ++iter;
    }

    cout << endl;

    //如果訪問非使用者編號和使用者姓名外的屬性,那就會重新查詢資料庫
    vector<UserModelApi*>::iterator iter2 = Users.begin();
    while(iter2 !=Users.end())
    {
        //cout << ((Proxy*)(*iter2))->toString() << endl;
        cout <<"userId:=" << (*iter2)->getUserId() << ", " <<
               "userName:=" << (*iter2)->getName()   << ", " <<
               "depId:= "<< (*iter2)->getDepId()  << endl;
        ++iter2;
    }
    return 0;
}

5. 相關模式

(1)代理模式與介面卡模式

  ①相似:都為另一個物件提供間接性的訪問,而且都是從自身以外的一個介面向另這個物件轉發請求。

  ②不同:介面卡模式主要用來解決介面之間不匹配的問題,它通常為所適配物件提供一個不同的介面;而代理模式會實現和目標物件相同的介面

(2)代理模式與裝飾模式

  ①相似:裝飾模式的實現和保護代理的實現上是類似的。都是在轉調其他物件的前後執行一定的功能。

  ②不同:裝飾模式的目的是為讓你不生成子類就可以給物件新增職責,也就是為了動態增加功能;而代理模式的主要目的是控制對物件的訪問(如當代理檢測到有敏感關鍵詞時,就直接返回,不會再呼叫真實的目標物件了,但裝飾模式的目的是增加新功能,所以一般得繼續呼叫真實目標物件,所以可以認為裝飾只是新增功能,但不能跳過原來的功能)。

  ③當實現代理模式的時候,我們常常在一個代理類中建立一個真實目標物件的例項,但裝飾模式時,我們通常的做法是將原始物件作為一個引數傳遞給裝飾者的構造器。