c++設計模式-裝飾器模式和代理模式

白伟碧一些小心得發表於2024-05-27
namespace _nmsp1
{
    //抽象的控制元件類
    class Control
    {
    public:
        virtual void draw() = 0; //draw方法,用於將自身繪製到螢幕上。
    public:
        virtual ~Control() {} //做父類時解構函式應該為虛擬函式
    };

    //列表控制元件類
    class ListCtrl :public Control
    {
    public:
        virtual void draw()
        {
            cout << "繪製普通的列表控制元件!" << endl; //具體可以用DirectX或OpenGL來繪製
        }
    };

    //抽象的裝飾器類
    class Decorator :public Control
    {
    public:
        Decorator(Control* tmpctrl) :m_control(tmpctrl) {} //建構函式
        virtual void draw()
        {
            m_control->draw(); //虛擬函式,呼叫的是哪個draw,取決於m_control指向的物件
        }
    private:
        Control* m_control; //需要被裝飾的其他控制元件,這裡用的是Control *;
    };

    //----------------------
    //具體的“邊框”裝飾器類
    class BorderDec :public Decorator
    {
    public:
        BorderDec(Control* tmpctrl) :Decorator(tmpctrl) {} //建構函式
        virtual void draw()
        {
            Decorator::draw(); //呼叫父類的draw方法以保持以往已經繪製出的內容
            drawBorder(); //也要繪製自己的內容
        }
    private:
        void drawBorder()
        {
            cout << "繪製邊框!" << endl;
        }
    };

    //具體的“垂直捲軸”裝飾器類
    class VerScrollBarDec :public Decorator
    {
    public:
        VerScrollBarDec(Control* tmpctrl) :Decorator(tmpctrl) {} //建構函式
        virtual void draw()
        {
            Decorator::draw(); //呼叫父類的draw方法以保持以往已經繪製出的內容
            drawVerScrollBar(); //也要繪製自己的內容
        }
    private:
        void drawVerScrollBar()
        {
            cout << "繪製垂直捲軸!" << endl;
        }
    };

    //具體的“水平捲軸”裝飾器類
    class HorScrollBarDec :public Decorator
    {
    public:
        HorScrollBarDec(Control* tmpctrl) :Decorator(tmpctrl) {} //建構函式
        virtual void draw()
        {
            Decorator::draw(); //呼叫父類的draw方法以保持以往已經繪製出的內容
            drawHorScrollBar(); //也要繪製自己的內容
        }
    private:
        void drawHorScrollBar()
        {
            cout << "繪製水平捲軸!" << endl;
        }
    };
}

int main()
{
    //(1)建立一個又帶邊框,又帶垂直捲軸的列表控制元件
    //首先繪製普通的列表控制元件
    _nmsp1::Control* plistctrl = new _nmsp1::ListCtrl(); //本體

    //接著“藉助普通的列表控制元件”,可以透過邊框裝飾器繪製出一個“帶邊框的列表控制元件”
    _nmsp1::Decorator* plistctrl_b = new _nmsp1::BorderDec(plistctrl); //_nmsp1::Decorator*用成_nmsp1::Control *

    //接著“藉助帶邊框的列表控制元件”,就可以透過垂直捲軸裝飾器繪製出一個“帶垂直捲軸又帶邊框的列表控制元件”
    _nmsp1::Decorator* plistctrl_b_v = new _nmsp1::VerScrollBarDec(plistctrl_b);
    plistctrl_b_v->draw(); //這裡完成最終繪製

    cout << "--------------------" << endl;
    //(2)建立一個只帶水平捲軸的列表控制元件
    //首先繪製普通的列表控制元件
    _nmsp1::Control* plistctrl2 = new _nmsp1::ListCtrl(); //本體

    //接著“藉助普通的列表控制元件”,可以透過水平捲軸裝飾器繪製出一個“帶水平捲軸的列表控制元件”
    _nmsp1::Decorator* plistctrl2_h = new _nmsp1::HorScrollBarDec(plistctrl2);
    plistctrl2_h->draw();

    //(3)釋放資源
    delete plistctrl_b_v;
    delete plistctrl_b;
    delete plistctrl;

    delete plistctrl2_h;
    delete plistctrl2;


}
  

裝飾(Decorator)模式:裝飾器模式/包裝模式,結構型模式

//(1)問題的提出
//UI(使用者介面)介面。
//a)ListCtrl類代表普通列表控制元件,提供draw方法
//b)BorderListCtrl類,繼承自ListCtrl,增加了邊框的列表控制元件,提供draw方法
//c)VerScBorderListCtrl類繼承自BorderListCtrl,表示增加了邊框又增加了垂直捲軸的列表控制元件,提供draw方法
//d)HorScVerScBorderListCtrl類,繼承自VerScBorderListCtrl,表示增加了邊框,垂直、水平捲軸的列表控制元件,提供draw方法
//繼承 改為 組裝方式來解決,防止類氾濫
//a)ListCtrl類代表普通列表控制元件,提供draw方法
//b)增加邊框->帶邊框的列表控制元件
//c)增加垂直捲軸->帶純質捲軸的列表控制元件,再給這個帶垂直捲軸的列表控制元件增加一個水平捲軸->既帶垂直捲軸又帶水平捲軸的列表控制元件。

//這種透過裝飾方式將一個類的功能不斷增強的思想(動態的增加新功能),就是裝飾模式的核心設計思想。
//public繼承:is -a 關係,組合關係和委託關係。
(2)引入裝飾(Decorator)模式
//組合複用原則(Composite Reuse Principle,CRP),也稱為合成複用原則/聚合複用原則。
//若兩個使用繼承進行設計,則父類程式碼的修改可能影響子類的行為,而且,可能父類中的很多方法子類是用不上的,這顯然是一種浪費,
//若使用組合進行設計,則可以大大降低兩個類之間的依賴關係,也不會存在因繼承關係導致的浪費行為,所以
//如果繼承和組合都能達到設計目的,優先考慮使用組合(組合優於繼承)。
//“裝飾”設計模式的定義(實現意圖):動態的給一個物件新增一些額外的職責。就增加功能來說,該模式相比生成子類更加靈活。
//裝飾模式包含的四種角色:
//a:Control(抽象構件):draw,讓呼叫者以一致的方式處理未被修飾的物件以及經過修飾之後的物件,實現客戶端的透明操作。
//b:ListCtrl(具體構件):實現抽象構件定義的介面,此後,裝飾器就可以給該構件增加額外的方法(職責);
//c:Decorator(抽象裝飾器類):
//d:BorderDec、VerScrollBarDec、HorScrollBarDesc(具體裝飾器類):增加了一些新方法,然後透過對draw介面的擴充套件,達到最終的修飾目的。
namespace _nmsp2
{
    //抽象飲料類
    class Beverage
    {
    public:
        virtual int getprice() = 0; //獲取價格
    public:
        virtual ~Beverage() {}
    };

    //水果飲料類
    class FruitBeverage : public Beverage
    {
    public:
        virtual int getprice()
        {
            return 10; //一杯單純的水果飲料,售價為10元
        }
    };

    //抽象的裝飾器類
    class Decorator :public Beverage
    {
    public:
        Decorator(Beverage* tmpbvg) :m_pbvg(tmpbvg) {} //建構函式
        virtual int getprice()
        {
            return m_pbvg->getprice();
        }
    private:
        Beverage* m_pbvg;
    };

    //具體的“砂糖”裝飾器類
    class SugarDec :public Decorator
    {
    public:
        SugarDec(Beverage* tmpbvg) :Decorator(tmpbvg) {} //建構函式
        virtual int getprice()
        {
            return Decorator::getprice() + 1; //額外加多1元,要呼叫父類的getprice方法以把以往的價格增加進來
        }
    };

    //具體的“牛奶”裝飾器類
    class MilkDesc :public Decorator
    {
    public:
        MilkDesc(Beverage* tmpbvg) :Decorator(tmpbvg) {} //建構函式
        virtual int getprice()
        {
            return Decorator::getprice() + 2; //額外加多2元,要呼叫父類的getprice方法以把以往的價格增加進來
        }
    };

    //具體的“珍珠”裝飾器類
    class BubbleDesc :public Decorator
    {
    public:
        BubbleDesc(Beverage* tmpbvg) :Decorator(tmpbvg) {} //建構函式
        virtual int getprice()
        {
            return Decorator::getprice() + 2; //額外加多2元,要呼叫父類的getprice方法以把以往的價格增加進來
        }
    };
}
int main()
{
//建立一杯單純的水果飲料,價格10元:
    _nmsp2::Beverage* pfruit = new _nmsp2::FruitBeverage();
    //向飲料中增加珍珠,價格多加了2元
    _nmsp2::Decorator *pfruit_addbubb = new _nmsp2::BubbleDesc(pfruit);
    //再向飲料中增加砂糖,價格又加多了1元
    _nmsp2::Decorator* pfruit_addbubb_addsugar = new _nmsp2::SugarDec(pfruit_addbubb);
    //輸出最終的價格
    cout << "加了珍珠又加了砂糖的水果飲料最終價格是:" << pfruit_addbubb_addsugar->getprice() << "元人民幣" << endl;

    //釋放資源
    delete pfruit_addbubb_addsugar;
    delete pfruit_addbubb;
    delete pfruit;

    return 0;


}

代理模式

namespace _nmsp1
{
    class WebAddr
    {
    public:
        virtual void visit() = 0; //執行訪問網站的動作,子類中會重新實現
        virtual ~WebAddr() {} //做父類時解構函式應該為虛擬函式
    };
    //某購物網站
    class WebAddr_Shopping :public WebAddr
    {
    public:
        virtual void visit()
        {
            //......訪問該購物網站,可能涉及複雜的網路通訊
            cout << "訪問WebAddr_Shopping購物網站!" << endl;
        }
    };
    //某影片網站
    class WebAddr_Video :public WebAddr
    {
    public:
        virtual void visit()
        {
            //......訪問該影片網站,可能涉及複雜的網路通訊
            cout << "訪問WebAddr_Video影片網站!" << endl;
        }
    };

    //-------------------------
    //網站程式碼類
    class WebAddrProxy :public WebAddr
    {
    public:
        //建構函式,引入的目的是傳遞進來要訪問的具體網站
        WebAddrProxy(WebAddr* pwebaddr) :mp_webaddr(pwebaddr) {}

    public:
        virtual void visit()
        {
            //在這裡進行訪問的合法性檢查,日誌記錄或者流量限制......
            mp_webaddr->visit();
            //在這裡可以進行針對返回資料的過濾......
        }

    private:
        WebAddr* mp_webaddr; //程式碼要訪問的具體網站
    };
    
    //-----------------
    //專門針對某購物網站WebAddr_Shopping的代理
    class WebAddr_Shopping_Proxy :public WebAddr
    {
    public:
        virtual void visit()
        {
            //在這裡進行訪問的合法性檢查,日誌記錄或者流量限制......
            WebAddr_Shopping* p_webaddr = new WebAddr_Shopping();
            p_webaddr->visit();            
            //在這裡可以進行針對返回資料的過濾......

            //釋放資源
            delete p_webaddr;
        }
    };
}


int main()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程式退出時檢測記憶體洩漏並顯示到“輸出”視窗
    

    _nmsp1::WebAddr* wba1 = new _nmsp1::WebAddr_Shopping();
    wba1->visit(); //訪問該購物網站

    _nmsp1::WebAddr* wba2 = new _nmsp1::WebAddr_Video();
    wba2->visit(); //訪問該影片網站

    //資源釋放
    delete wba1;
    delete wba2;


    /*
    _nmsp1::WebAddr* wba1 = new _nmsp1::WebAddr_Shopping();
    _nmsp1::WebAddr* wba2 = new _nmsp1::WebAddr_Video();

    _nmsp1::WebAddrProxy* wbaproxy1 = new _nmsp1::WebAddrProxy(wba1);
    wbaproxy1->visit(); //透過代理去訪問WebAddr_Shopping購物網站

    _nmsp1::WebAddrProxy* wbaproxy2 = new _nmsp1::WebAddrProxy(wba2);
    wbaproxy2->visit(); //透過代理去訪問WebAddr_Video影片購物網站

    //資源釋放
    delete wba1;
    delete wba2;
    //資源釋放
    delete wbaproxy1;
    delete wbaproxy2;
    */

    /*
    _nmsp1::WebAddr_Shopping_Proxy* wbasproxy = new _nmsp1::WebAddr_Shopping_Proxy();
    wbasproxy->visit(); //訪問實際的購物網站

    //資源釋放
    delete wbasproxy;
    */
    
    return 0;
}
代理(Proxy)模式:結構型模式
//目的:為客戶端增加額外的功能、約束或針對客戶端的呼叫遮蔽一些複雜的細節問題。
//(1)基本概念和範例
//透過引入代理類來為原始類(被代理類)增加額外的能力——新功能、新服務,約束或者限制。
//使用代理類的程式設計師與開發這個代理類的程式設計師並不是同一個程式設計師甚至可能是兩個不同公司的程式設計師。

(2)引入代理(Proxy)模式:
//定義(實現意圖):為其他物件提供一種代理以控制對這個物件的訪問。
//三種角色:
//a)Subject(抽象主題),WebAddr類。
//b)Proxy(代理主題):WebAddr_Shopping_Proxy類。
//c)RealSubject(真實主題):WebAddr_Shopping

//代理模式和裝飾模式對比;
//代理模式:代表真實主題並給真實主題增加一些新能力或者責任,這些功能與真實主題要實現的功能可能沒有相關性,主要是控制對真實主題的訪問。
//裝飾模式:新功能與原有構件能力具有相關性,是對原有構件能力或者行為的擴充套件(增強)。

(3)代理模式的應用場合探究
//( 3.1 )代理模式常用應用場景
//常見的代理包括但不限於如下這些型別:
//a)遠端代理(Remote Proxy)
//b)虛擬代理(Virtual Proxy)
//c)保護代理(Protect Proxy)
//d)快取/緩衝代理(Cache Proxy)
//e)智慧引用代理(Smart Reference Proxy)。shared_ptr
//f)寫時複製(copy-on-write)最佳化代理:string
//g)防火牆代理、同步代理,複雜隱藏代理等。
namespace _nmsp2
{
    //快取/緩衝代理(Cache Proxy)範例
    vector<string> g_fileItemList; 

    //抽象主題
    class ReadInfo
    {
    public:
        virtual void read() = 0;
        virtual ~ReadInfo() {}
    };
    //真實主題
    class ReadInfoFromFile : public ReadInfo
    {
    public:
        virtual void read() //從檔案中讀資訊(讀取test.txt的內容)
        {
            ifstream fin("test.txt");
            if (!fin)
            {
                cout << "檔案開啟失敗" << endl;
                return;
            }
            string linebuf;
            while (getline(fin, linebuf)) //從哪個檔案中逐行讀入內容
            {
                if (!linebuf.empty()) //讀的不是空行
                {
                    g_fileItemList.push_back(linebuf); //將檔案中的每一行都儲存到vector容器中。
                    //cout << linebuf << endl;
                }
            }
            fin.close(); //關閉檔案輸入流
        }
    };
    //代理主題
    class ReadInfoProxy :public ReadInfo
    {
    public:
        virtual void read()
        {
            if (!m_loaded)
            {
                //沒有從檔案中載入資訊,則載入
                m_loaded = true; //標記已經從檔案中載入資訊了
                cout << "從檔案中讀取了如下資料---------:" << endl;
                ReadInfoFromFile* rf = new ReadInfoFromFile();
                rf->read(); //將檔案中的資料讀入全域性容器g_fileItemList
                delete rf; //釋放資源
            }
            else
            {
                cout << "從快取中讀取了如下資料----------:" << endl;
            }
            //現在資料一定在g_fileItemList中,開始顯示
            for (auto iter = g_fileItemList.begin(); iter != g_fileItemList.end(); ++iter)
            {
                cout << *iter << endl;
            }
        }
    private:
        bool m_loaded = false; //false表示還沒有從檔案中讀出資料到記憶體
    };

}


int main()
{
    
    _nmsp2::ReadInfo* preadinfoproxy = new _nmsp2::ReadInfoProxy();
    preadinfoproxy->read(); //第一次呼叫read是藉助代理使用真實主題到檔案中拿資料
    preadinfoproxy->read(); //後續呼叫read都是直接從快取中拿資料
    preadinfoproxy->read(); //從快取中拿資料

    //資源釋放
    delete preadinfoproxy;
    system("pause");
    return 0;


}

裝飾器模式和代理模式在設計模式中有不同的應用場景和作用。

裝飾器模式(Decorator Pattern)主要用於動態地給物件新增額外的職責。它允許向一個現有的物件新增新的功能,而無需改變其結構。裝飾器模式透過建立一個裝飾器類,該類實現了與被裝飾物件相同的介面,並在內部持有一個被裝飾物件的例項,從而可以動態地新增新的功能。這樣就可以在執行時動態地新增、移除或者組合物件的行為,而不影響其原始行為。

代理模式(Proxy Pattern)則是用一個代理物件來控制對原始物件的訪問。代理模式可以用於控制對物件的訪問,以實現對原始物件的操作的增強、延遲載入或者許可權控制。代理模式常見的應用包括遠端代理、虛擬代理、保護代理等。代理物件通常會持有對真實物件的引用,並在對真實物件的訪問上進行控制,例如在對真實物件的請求前後執行額外的邏輯。

因此,裝飾器模式主要用於動態地擴充套件物件的功能,而代理模式主要用於控制對物件的訪問。兩種模式在應用場景和解決問題上有一定的差異,但都能夠提供靈活性和可維護性。

當使用C++實現裝飾器模式時,我們可以考慮一個簡單的示例:假設我們有一個Shape介面和其具體實現類Circle,現在我們希望能夠動態地給Circle新增額外的功能,比如顏色或者邊框。這時候就可以使用裝飾器模式。

首先,我們定義Shape介面及其具體實現類Circle

// Shape介面
class Shape {
public:
    virtual void draw() = 0;
};

// 具體的形狀類:圓形
class Circle : public Shape {
public:
    void draw() override {
        std::cout << "畫一個圓形" << std::endl;
    }
};

然後,我們建立一個裝飾器類ShapeDecorator,用於動態地新增額外的功能。這裡以新增顏色為例:

// 裝飾器類
class ShapeDecorator : public Shape {
protected:
    Shape* decoratedShape;

public:
    ShapeDecorator(Shape* shape) : decoratedShape(shape) {}

    void draw() override {
        decoratedShape->draw();
    }
};

// 具體的裝飾器類:新增顏色
class ColorDecorator : public ShapeDecorator {
private:
    std::string color;

public:
    ColorDecorator(Shape* shape, const std::string& col) : ShapeDecorator(shape), color(col) {}

    void draw() override {
        decoratedShape->draw();
        std::cout << "顏色: " << color << std::endl;
    }
};

使用裝飾器模式,我們可以這樣來使用:

int main() {
    // 建立一個原始的圓形物件
    Shape* circle = new Circle();

    // 使用裝飾器來動態地新增顏色
    Shape* redCircle = new ColorDecorator(circle, "紅色");
    redCircle->draw();

    delete circle;
    delete redCircle;

    return 0;
}

在這個示例中,我們透過裝飾器模式動態地給Circle物件新增了顏色的功能,而不需要修改Circle類的結構。

當使用C++實現裝飾器模式時,我們可以考慮一個簡單的示例:假設我們有一個Shape介面和其具體實現類Circle,現在我們希望能夠動態地給Circle新增額外的功能,比如顏色或者邊框。這時候就可以使用裝飾器模式。

首先,我們定義Shape介面及其具體實現類Circle

cppCopy Code
// Shape介面
class Shape {
public:
    virtual void draw() = 0;
};

// 具體的形狀類:圓形
class Circle : public Shape {
public:
    void draw() override {
        std::cout << "畫一個圓形" << std::endl;
    }
};

然後,我們建立一個裝飾器類ShapeDecorator,用於動態地新增額外的功能。這裡以新增顏色為例:

// 裝飾器類
class ShapeDecorator : public Shape {
protected:
    Shape* decoratedShape;

public:
    ShapeDecorator(Shape* shape) : decoratedShape(shape) {}

    void draw() override {
        decoratedShape->draw();
    }
};

// 具體的裝飾器類:新增顏色
class ColorDecorator : public ShapeDecorator {
private:
    std::string color;

public:
    ColorDecorator(Shape* shape, const std::string& col) : ShapeDecorator(shape), color(col) {}

    void draw() override {
        // 呼叫被裝飾物件的draw方法
        decoratedShape->draw();
        // 新增額外的功能:顏色
        std::cout << "顏色: " << color << std::endl;
    }
};

使用裝飾器模式,我們可以這樣來使用:

cppCopy Code
int main() {
    // 建立一個原始的圓形物件
    Shape* circle = new Circle();

    // 使用裝飾器來動態地新增顏色
    Shape* redCircle = new ColorDecorator(circle, "紅色");
    redCircle->draw();

    delete circle;
    delete redCircle;

    return 0;
}

在這個示例中,我們透過裝飾器模式動態地給Circle物件新增了顏色的功能,而不需要修改Circle類的結構。這樣做的好處在於,可以在執行時動態地新增新的功能,而且還能保持類的單一職責原則,使得程式碼更加靈活和可維護。

當使用C++實現代理模式時,我們可以考慮一個簡單的示例:假設我們有一個介面Image和其具體實現類RealImage,現在我們希望能夠透過代理來控制對RealImage物件的訪問。這時候就可以使用代理模式。

首先,我們定義Image介面及其具體實現類RealImage

// Image介面
class Image {
public:
    virtual void display() = 0;
};

// 具體的影像類:實際影像
class RealImage : public Image {
private:
    std::string filename;

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

    void display() override {
        std::cout << "顯示影像: " << filename << std::endl;
    }
};

然後,我們建立一個代理類ProxyImage,用於控制對RealImage物件的訪問:

// 代理類
class ProxyImage : public Image {
private:
    std::string filename;
    RealImage* realImage;

    void loadImage() {
        if (realImage == nullptr) {
            realImage = new RealImage(filename);
        }
    }

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

    void display() override {
        loadImage();
        realImage->display();
    }
};

使用代理模式,我們可以這樣來使用:

int main() {
    // 使用代理來控制對RealImage物件的訪問
    Image* image = new ProxyImage("test.jpg");

    // 第一次訪問,會載入真實影像
    image->display();

    // 第二次訪問,不需要重新載入真實影像
    image->display();

    delete image;

    return 0;
}

在這個示例中,我們透過代理模式控制了對RealImage物件的訪問。代理類ProxyImage負責在必要時建立並管理RealImage物件,並在適當的時機呼叫RealImage物件的方法。透過代理模式,我們可以實現延遲載入、訪問控制等功能,而不需要直接訪問RealImage物件。




相關文章