裝飾者模式(Decorator Pattern)

裝飾者模式:動態的將責任加諸於物件上,想要擴充功能,裝飾者提供有別於繼承的另一個選擇。

我們用個人化披薩來解釋裝飾者模式,假使餐廳販售某種消費者自製披薩,消費者選擇披薩的樣式,之後從眾多配料中選幾種自己喜歡的,我們的目標就是當消費者選好披薩時,可以得知披薩的價錢和名稱,價錢是樣式和選擇配料價錢的總和,名稱是樣式加上挑選的配料名稱。

由於配料眾多,所以可能選到普通披薩、番茄青醬披薩、洋蔥蛋青醬披薩等,假使我們要為每種可能性定一個類別,類別數目會多到難以撰寫,且容易有遺漏,以下為其中三種類別。

class Pizza{
public:
    virtual int getPrice() = 0;
    virtual void getName() = 0;
};
class NormalPizza : public Pizza{
    int getPrice(){return 15;}
    void getName(){cout << "普通披薩" << endl;}
};
class TomatoPestoPizza: public Pizza{
    int getPrice(){return 50;}
    void getName(){cout << "番茄青醬披薩" << endl;}
};
class OnionEggPestoPizza: public Pizza{
    int getPrice(){return 55;}
    void getName(){cout << "洋蔥蛋青醬披薩" << endl;}
};

我們可以改變上述的架構,披薩類別包含所有配料,以及價錢和命名的方式,披薩子類別為各種樣式的披薩,實例決定是否要某個配料,最後經由選擇的配料,得到價錢和名稱,這個方法較第一個好,但是假使消費者想要配料加倍(比如雙份洋蔥),或是需要更彈性的命名方式,都會讓我們不好更改,以下為程式碼。

#include<iostream>
#include<string>
using namespace std; 

class Pizza{
protected:
    bool m_onion;
    bool m_egg;
    bool m_tomate;
    int m_basicPrice;
    string m_basicName;
public:
    Pizza();
    void addOnion(){m_onion=true;}
    void addEgg(){m_egg=true;}
    void addTomate(){m_tomate=true;}
    int getPrice();
    string getName();
};
class NormalPizza : public Pizza{
public:
    NormalPizza();
};
class PestoPizza: public Pizza{
public:
    PestoPizza();
};

Pizza::Pizza(){
    m_onion = false;
    m_egg = false;
    m_tomate = false;
    m_basicPrice = 0;
    m_basicName = "";
}
int Pizza::getPrice(){
    int ret = m_basicPrice;
    if(m_onion){
        ret += 10;
    }
    if(m_egg){
        ret += 10;
    }
    if(m_tomate){
        ret += 15;
    }
    return ret;
}
string Pizza::getName(){
    string ret = m_basicName;
    if(m_onion){
        ret = "洋蔥" + ret;
    }
    if(m_egg){
        ret = "蛋" + ret;
    }
    if(m_tomate){
        ret = "番茄" + ret;
    }
    return ret;
}
NormalPizza::NormalPizza(){
    m_basicPrice = 15;
    m_basicName = "普通披薩";
}
PestoPizza::PestoPizza(){
    m_basicPrice = 30;
    m_basicName = "青醬披薩";
}
int main() { 
    PestoPizza myPizza;
    myPizza.addTomate();
    myPizza.addOnion();
    int price = myPizza.getPrice();
    string name = myPizza.getName();
    return 0; 
}

最後看看裝飾者模式,我們對所有口味定義一個類別,這些類別都繼承Pizza類別,將所有配料定義一個類別,這些類別都繼承Topping類別,假使我們選擇普通披薩,希望添加洋蔥在這個披薩上,就建立NormalPizza和Onion實例,並把NormalPizza包在Onion內,假使還需要來點番茄,就建立了Tomato實例,並將以上兩個物件包含在內。

假使我們選擇完成,口味和附加上的配料都包含getPrice()方法,透過層層包裝,getPrice()方法從目前價錢加上該配料的價錢後回傳,從外而內呼叫,最後得到總價錢,名稱也是用同樣的概念得到,以下為程式碼。

#include<iostream>
#include<string>
using namespace std; 

class Pizza{
protected:
    int m_price;
    string m_name;
public:
    Pizza(){m_price=0; m_name="";}
    virtual int getPrice(){return m_price;}
    virtual string getName(){return m_name;}
};
class NormalPizza : public Pizza{
public:
    NormalPizza(){m_price=15; m_name="普通披薩";}
};
class PestoPizza: public Pizza{
public:
    PestoPizza(){m_price=30; m_name="青醬披薩";}
};

class Topping : public Pizza{
};
class Onion : public Topping{
public:
    Pizza *m_pizza;
    Onion(Pizza *pizza){
        m_pizza = pizza;
    }
    int getPrice(){return 10 + m_pizza->getPrice();}
    string getName(){return "洋蔥" + m_pizza->getName();}
};
class Egg : public Topping{
public:
    Pizza *m_pizza;
    Egg(Pizza *pizza){
        m_pizza = pizza;
    }
    int getPrice(){return 10 + m_pizza->getPrice();}
    string getName(){return "蛋" + m_pizza->getName();}
};
class Tomato : public Topping{
public:
    Pizza *m_pizza;
    Tomato(Pizza *pizza){
        m_pizza = pizza;
    }
    int getPrice(){return 15 + m_pizza->getPrice();}
    string getName(){return "番茄" + m_pizza->getName();}
};
int main() { 
    Pizza *myPizza = newPestoPizza();
    Pizza *temp = myPizza;
    myPizza = &Onion(myPizza);
    myPizza = &Tomato(myPizza);
    int price = myPizza->getPrice();
    string name = myPizza->getName();
    delete temp;
    return 0; 
}