狀態模式(State Pattern)

狀態模式:允許物件隨著內部狀態改變而改變行為,就好像物件的類別改變一樣。

有時候成員函式內部,有許多if else的判斷式,後續更新可能持續增加判斷式,以至於最後變得冗長不好維護,這時可以用狀態模式將每個狀態視為一個獨立物件,讓後續需求更動能更好維護,不過也會造成類別變多的情況。

我們用門票販賣機來解釋狀態模式,販賣機有四種狀態,分別是有錢、沒錢、售出門票、門票售完,使用者有投錢和按下買票鍵兩個選擇,販賣機根據目前狀態,會對使用者行為產生對應的結果,我們用TicketMachine類別表示我們的販賣機,建構式參數代表門票數,預設狀態為沒錢,以下為程式碼。

#include <iostream>
using namespace std;

class TicketMachine{
private:
    const static int SOLD_OUT = 0;
    const static int NO_MONEY = 1;
    const static int HAS_MONEY = 2;
    const static int SOLD = 3;
    int m_count;
    int m_state;
public:
    TicketMachine(int count){
        m_count = count;
        m_state = NO_MONEY;
    }
    void insertMoney();
    void buyTicket();
    void dispense();
};
void TicketMachine::insertMoney(){
    if (m_state == HAS_MONEY){ 
        cout << "禁止投錢,已有鈔票" << endl; 
    }
    else if (m_state == NO_MONEY){ 
        m_state = HAS_MONEY; 
        cout << "投入鈔票" << endl; 
    }
    else if (m_state == SOLD_OUT){ 
        cout << "禁止投錢,票已售完" << endl; 
    }
    else if (m_state == SOLD){
        cout << "稍待片刻" << endl;
    }
}
void TicketMachine::buyTicket(){
    if (m_state == HAS_MONEY){
        cout << "買票成功" << endl;
        m_state = SOLD;
        dispense();
    }
    else if (m_state == NO_MONEY){
        cout << "買票失敗,您沒投錢" << endl;
    }
    else if (m_state == SOLD_OUT){
        cout << "買票失敗,票已售完" << endl;
    }
    else if (m_state == SOLD){
        cout << "買票失敗" << endl;
    }
}
void TicketMachine::dispense(){
    cout << "正在售票" << endl;
    m_count--;
    if (m_count == 0){
        m_state = SOLD_OUT;
        cout << "票已售完" << endl;
    }
    else{
        m_state = NO_MONEY;
    }
}
int main(){
    TicketMachine myTicketMachine(2); //預設有2張票
    myTicketMachine.buyTicket();
    myTicketMachine.insertMoney();
    myTicketMachine.buyTicket();
    myTicketMachine.insertMoney();
    myTicketMachine.buyTicket();
    
    return 0;
}

假使我們之後要增加功能,上面的設計除了增加一個狀態,buyTicket()和insertMoney()函式都必須加上更多的判斷式和處理,這邊使用狀態模式,讓每一個狀態實踐各自的動作,以下為程式碼。

#include <iostream>
using namespace std;

class State{
public:
    virtual void insertMoney() = 0;
    virtual void buyTicket() = 0;
};
class Machine{
public:
    State *m_soldOutState;
    State *m_noMoneyState;
    State *m_hasMoneyState;
    State *m_soldState;
    virtual void insertMoney() = 0;
    virtual void buyTicket() = 0;
    virtual void dispense() = 0;
    virtual void setState(State *state) = 0;
};

class NoMoneyState : public State{
private:
    Machine *m_ticketMachine;
public:
    NoMoneyState(Machine *machine){ m_ticketMachine = machine; }
    void insertMoney(){
        m_ticketMachine->setState(m_ticketMachine->m_hasMoneyState);
        cout << "投入鈔票" << endl;
    }
    void buyTicket(){ cout << "買票失敗,您沒投錢" << endl; }
};
class HasMoneyState : public State{
private:
    Machine *m_ticketMachine;
public:
    HasMoneyState(Machine *machine){ m_ticketMachine = machine; }
    void insertMoney(){ cout << "禁止投錢,已有鈔票" << endl; }
    void buyTicket(){
        cout << "買票成功" << endl;
        m_ticketMachine->setState(m_ticketMachine->m_soldState);
        m_ticketMachine->dispense();
    }
};
class SoldState : public State{
private:
    Machine *m_ticketMachine;
public:
    SoldState(Machine *machine){ m_ticketMachine = machine; }
    void insertMoney(){cout << "稍待片刻" << endl;}
    void buyTicket(){cout << "買票失敗" << endl;}
};
class SoldOutState : public State{
private:
    Machine *m_ticketMachine;
public:
    SoldOutState(Machine *machine){ m_ticketMachine = machine; }
    void insertMoney(){ cout << "禁止投錢,票已售完" << endl; }
    void buyTicket(){ cout << "買票失敗,票已售完" << endl; }
};

class TicketMachine : public Machine{
private:
    State * m_state;
    int m_count;
public:
    TicketMachine(int count){
        m_count = count;
        m_soldOutState = new SoldOutState(this);
        m_noMoneyState = new NoMoneyState(this);
        m_hasMoneyState = new HasMoneyState(this);
        m_soldState = new SoldState(this);
        m_state = m_noMoneyState;
    }
    ~TicketMachine(){
        delete m_soldOutState;
        delete m_noMoneyState;
        delete m_hasMoneyState;
        delete m_soldState;
    }
    void insertMoney(){ m_state->insertMoney(); }
    void buyTicket(){ m_state->buyTicket(); }
    void dispense();
    void setState(State *state){ m_state = state; }
};
void TicketMachine::dispense(){
    cout << "正在售票" << endl;
    m_count--;
    if (m_count == 0){
        m_state = m_soldOutState;
        cout << "票已售完" << endl;
    }
    else{
        m_state = m_noMoneyState;
    }
}

int main(){
    TicketMachine myTicketMachine(2); //預設有2張票
    myTicketMachine.buyTicket();
    myTicketMachine.insertMoney();
    myTicketMachine.buyTicket();
    myTicketMachine.insertMoney();
    myTicketMachine.buyTicket();
    
    return 0;
}

樣板方法模式(Template Method Pattern)

樣板方法模式:將一個演算法的骨架定義在一個方法中,而其中的某些方法定義在次類別中,樣板方法讓次類別可以在不改變演算法架構的情況下,重新定義演算法中的某些步驟。

我們用泡茶和咖啡來解釋樣板方法模式,假設我們有以下兩個類別Coffee和Tea,我們可以看出兩個類別提供的方法,有很多類似的地方。

class Coffee{
public:
    void prepareRecipe(){
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
    }
    void boilWater(){ cout << "煮開水" << endl; }
    void brewCoffeeGrinds(){ cout << "煮咖啡" << endl; }
    void pourInCup(){ cout << "倒入杯中" << endl; }
};

class Tea{
public:
    void prepareRecipe(){
        boilWater();
        steepTeaBag();
        pourInCup();
    }
    void boilWater(){ cout << "煮開水" << endl; }
    void steepTeaBag(){ cout << "泡茶" << endl; }
    void pourInCup(){ cout << "倒入杯中" << endl; }
};

這時我們可以用樣板方法模式,創造一個Beverage類別,將Coffee和Tea類別相同的地方,抽取到Beverage類別內。

#include <iostream>
using namespace std;

class Beverage{
public:
    void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
    }
    void boilWater(){ cout << "煮開水" << endl; }
    virtual void brew() = 0;
    void pourInCup(){ cout << "倒入杯中" << endl; }
};
class Coffee : public Beverage{
public:
    void brew(){ cout << "煮咖啡" << endl; }
};

class Tea : public Beverage{
public:
    void brew(){ cout << "泡茶" << endl; }
};

int main(){
    Coffee myCoffee;
    Tea myTea;
    myCoffee.prepareRecipe();
    myTea.prepareRecipe();

    return 0;
}

表象模式(Facade Pattern)

表象模式:提供了一個統一的介面,用來存取次系統的一群介面,讓次系統共容易使用。

我們以泡咖啡來形容表象模式,假設泡咖啡有以下幾種流程:
1.煮開水
2.準備咖啡粉
3.倒入熱水
4.攪拌

我們可以使用表象模式,實踐一個表象類別,提供一個易操作的介面來簡化原先的複雜介面,如果我們需要次系統的複雜操作,可以使用原來的介面,如果需要一個方便使用的介面,就使用這個新的表象類別,以下為程式碼。

#include <iostream>
using namespace std;

class Boil{
public:
    void boilWater(){ cout << "煮開水" << endl; }
};
class Coffee{
public:
    void blueMountain(){ cout << "準備藍山咖啡" << endl; }
    void mandheling(){ cout << "準備曼特寧咖啡" << endl; }
};
class Pour{
public:
    void pourHotWater(){ cout << "倒熱水" << endl; }
    void pourColdWater(){ cout << "倒冷水" << endl; }
};
class Stir{
public:
    void fastStir(){ cout << "快速攪拌" << endl; }
    void slowStir(){ cout << "慢慢攪拌" << endl; }
};

class MakeCoffee{
private:
    Boil m_boil;
    Coffee m_coffee;
    Pour m_pour;
    Stir m_stir;
public:
    MakeCoffee(Boil, Coffee, Pour, Stir);
    void process();
};
MakeCoffee::MakeCoffee(Boil boil, Coffee coffee, Pour pour, Stir stir){
    m_boil = boil;
    m_coffee = coffee;
    m_pour = pour;
    m_stir = stir;
}
void MakeCoffee::process(){
    m_boil.boilWater();
    m_coffee.blueMountain();
    m_pour.pourHotWater();
    m_stir.slowStir();
}

int main(){
    Boil boil; 
    Coffee coffee; 
    Pour pour; 
    Stir stir;
    MakeCoffee myCoffee(boil, coffee, pour, stir);
    myCoffee.process();
    
    return 0;
}

轉接器模式(Adapter Pattern)

轉接器模式:將一個類別的介面,轉換成另一個介面以供客戶使用,轉接器讓原本不相容的類別可以互相合作。

假使有一個既有的軟體系統,想要和新的廠商類別庫搭配使用,我們不能改變廠商提供的程式碼,如果也不想改變原本的系統,這時可以寫一個類別,將廠商的介面轉成和舊系統相容的介面。

下面例子有類別狼和狗,這兩個類別彼此不相容,我們建了一個轉接器,將狗類別的成員函式,讓狼類別去實作,而testDog()函式,可以接受轉接器和狗類別當參數,所以我們不需要改變類別,以及testDog()函式的程式碼,就能達到目的。

#include <iostream>
using namespace std;

class Dog{
public:
    virtual void bark(){ cout << "旺旺" << endl; }
    virtual void run(){ cout << "跑一段距離" << endl; }
};
class Wolf{
public:
    virtual void howl(){ cout << "嚎~~" << endl; }
    virtual void run(){ cout << "跑一大段距離" << endl; }
};
class WolfAdapter : public Dog{
private:
    Wolf *m_wolf;
public:
    WolfAdapter(Wolf *wolf){ m_wolf = wolf; }
    void bark(){ m_wolf->howl(); }
    void run(){ m_wolf->run(); }
};

void testDog(Dog *dog);

int main(){
    Dog myDog;
    Wolf myWolf;
    WolfAdapter myWolfAdapter(&myWolf);

    testDog(&myDog);
    testDog(&myWolfAdapter);

    return 0;
}

void testDog(Dog *dog){
    dog->bark();
    dog->run();
}

我們也可以設計一個雙向轉接器,讓他同時可當作兩個類別的介面,以下是簡單的示範。

#include <iostream>
using namespace std;

class Dog{
public:
    virtual void bark(){ cout << "旺旺" << endl; }
};
class Wolf{
public:
    virtual void howl(){ cout << "嚎~~" << endl; }
};
class Adapter : public Dog, public Wolf{
private:
    Wolf *m_wolf;
    Dog *m_dog;
public:
    Adapter(Wolf *wolf){ m_wolf = wolf; }
    Adapter(Dog *dog){ m_dog = dog; }
    void bark(){ m_wolf->howl(); }
    void howl(){ m_dog->bark(); }
};

void testDog(Dog *dog);
void testWolf(Wolf *wolf);

int main(){
    Dog myDog;
    Wolf myWolf;
    Adapter dogAdapter(&myWolf);
    Adapter wolfAdapter(&myDog);

    testDog(&dogAdapter);
    testWolf(&wolfAdapter);

    return 0;
}

void testDog(Dog *dog){
    dog->bark();
}
void testWolf(Wolf *wolf){
    wolf->howl();
}

命令模式(Command Pattern)

命令模式:將請求封裝成物件,可以讓我們使用不同的請求、佇列、或者日誌來參數化其他物件。

我們用遙控器來解釋命令模式,假使要設計操作家電的遙控器,按鍵代表操作某個家電,我們不將操作細節寫在搖控器物件上,而是將命令和按鍵綁定,當我們按下按鍵時,透過之前綁定的命令,遙控器知道要將訊息通知哪個物件,由此物件進行實際的操作。

實作上我們定義Command命令類別,命令實例皆繼承於此類別,命令實例透過傳入參數得到接收者,execute()方法定義實際的操作,用RemoteControl類別的setCommand()方法,將某個命令和遙控器相關,Light和Door是接收命令的接收者,並進行實際的操作,以下為程式碼。

#include <iostream>
using namespace std;

class Light{
public:
    void on(){cout<<"開燈"<on();}
};
class DoorOpenCommand : public Command{
private:
    Door *m_door;
public:
    DoorOpenCommand(Door *door){m_door=door;}
    void execute(){m_door->open();}
};

class RemoteControl{
private:
    Command *slot;
public:
    void setCommand(Command *command){slot=command;}
    void pressButton(){slot->execute();}
};

int main(){
    RemoteControl myControl;
    Light myLight;
    Door myDoor;
    Light OnCommandcommand1(&myLight);
    Door OpenCommandcommand2(&myDoor);

    myControl.setCommand(&command1);
    myControl.pressButton();
    myControl.setCommand(&command2);
    myControl.pressButton();
    return 0;
}

下面實作有多個按鍵的遙控器,可以存放3個開與關的命令,我們使用陣列記錄這些命令,以下為程式碼。

#include <iostream>
using namespace std;

class Light{
public:
    void on(){ cout << "開燈" << endl; }
    void off(){ cout << "關燈" << endl; }
};
class Door{
public:
    void open(){ cout << "開門" << endl; }
    void close(){ cout << "關門" << endl; }
};

class Command{
public:
    virtual void execute() = 0;
};
class NoCommand: public Command{
public:
    void execute(){ cout << "尚未綁定命令" << endl; }
};
class LightOnCommand: public Command{
private:
    Light *m_light;
public:
    LightOnCommand(Light *light){ m_light = light; }
    void execute(){ m_light->on(); }
};
class LightOffCommand: public Command{
private:
    Light *m_light;
public:
    LightOffCommand(Light *light){ m_light = light; }
    void execute(){ m_light->off(); }
};
class DoorOpenCommand: public Command{
private:
    Door *m_door;
public:
    DoorOpenCommand(Door *door){ m_door = door; }
    void execute(){ m_door->open(); }
};
class DoorCloseCommand: public Command{
private:
    Door *m_door;
public:
    DoorCloseCommand(Door *door){ m_door = door; }
    void execute(){ m_door->close(); }
};

class RemoteControl{
private:
    Command *m_onCommands[3];
    Command *m_offCommands[3];
public:
    RemoteControl(Command *);
    void setCommand(int slot, Command *on, Command *off);
    void pressOnButton(int slot){ m_onCommands[slot]->execute(); }
    void pressOffButton(int slot){ m_offCommands[slot]->execute(); }
};
RemoteControl::RemoteControl(Command *command){
    for (int i = 0; i < 3; i++){
        m_onCommands[i] = command;
        m_offCommands[i] = command;
    }
}
void RemoteControl::setCommand(int slot, Command *on, Command *off){
    m_onCommands[slot] = on;
    m_offCommands[slot] = off;
}

int main(){
    Light myLight;
    Door myDoor;
    LightOnCommand command1(&myLight);
    LightOffCommand command2(&myLight);
    DoorOpenCommand command3(&myDoor);
    DoorCloseCommand command4(&myDoor);

    NoCommand nocommand;
    RemoteControl myControl(&nocommand);
    myControl.setCommand(0, &command1, &command2);
    myControl.setCommand(1, &command3, &command4);
    for (int i = 0; i < 3; i++){
        myControl.pressOnButton(i);
        myControl.pressOffButton(i);
    }
    system("PAUSE");
    return 0;
}

單例模式(Singleton Pattern)

實現單例模式的思路是:一個類別只有一個實例,並提供全域點存取此實例。

有些物件只需要一個實例,比如設定和登入的物件、和驅動程式溝通的物件、執行緒池等,單例模式可以在任何地方存取這個實體,像全域變數一樣方便,且需要時才建立物件,不像全域變數程式一開始就建好物件,造成可能的資源浪費。以下我們示範單例模式的使用,由於一次只會有一個實例產生,所以兩個instance有相同的位址,以下為程式碼。

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

class Singleton {
private:
    static Singleton *m_instance;
    Singleton(){}
public:
    static Singleton *getInstance(){
        if(m_instance==NULL){
            m_instance = new Singleton();
        }
        return m_instance;
    }
};
Singleton* Singleton::m_instance = NULL;

int main(){
    Singleton *instance1 = Singleton::getInstance();
    Singleton *instance2 = Singleton::getInstance();
    int addr1 = (int)instance1;
    int addr2 = (int)instance2;
    delete instance1;
    return 0;
}

單例模式在多執行緒的應用場合下要小心,當唯一實例尚未創建時,如果有兩個執行緒同時調用創建方法,可能各自創建了一個實例,這樣就有兩個實例被構造出來,違反了單例模式的原則,有以下三種解決方式:
1.對getInstance()這個函式同步化,不過會造成程式執行效率降低,如果需要頻繁運作此函式,可能需要重新考慮。
2.率先建立實例,而不用拖延實體化的作法,如以下所示:

class Singleton {
private:
    static Singleton *m_instance = new Singleton();
    Singleton(){}
public:
    static Singleton *getInstance(){
        return m_instance;
    }
};

3.利用雙重檢查上鎖,在getInstance()中減少使用同步化,檢查實例是否已經建立,沒建立的話才進行同步化,如此一來只有第一次會同步化,如以下所示:

class Singleton {
private:
    //volatile不會對m_instance進行編譯器最佳化,確保多執行緒處理m_instance是正確的
    volatile static Singleton *m_instance;
    Singleton(){}
public:
    static Singleton *getInstance(){
        if(m_instance==NULL){
            //下面進行同步化
            m_instance = new Singleton();
        }
        return m_instance;
    }
};

簡單工廠模式(Simple Factory Pattern)

簡單工廠模式算是最基本的工廠模式,經常使用到,也可以把這當成是一種編成習慣,我們用披薩店的情境來加以說明。

假設有一間披薩店,賣各式各樣的披薩,如果有人點了夏威夷披薩,就準備夏威夷披薩,送入烤箱烘烤,然後拿給消費者,假如有人點鮪魚披薩,就準備鮪魚披薩,接著烘烤並拿給消費者。

#include <iostream>
#include <string>
using namespace std;
 
class Pizza{
public:
    string m_name;
    void bake(){cout << m_name << "進行烘烤" << endl;}
    void deliver(){cout << m_name << "進行運送" << endl;}
};
class HawaiiPizza : public Pizza{
public:
    HawaiiPizza(){m_name="Hawaii";}
};
class TunaPizza : public Pizza{
public:
    TunaPizza(){m_name="Tuna";}
};

Pizza *orderPizza(string name){
    Pizza *ret;
    if(name=="Hawaii"){
        ret = new HawaiiPizza();
    }
    if(name=="Tuna"){
        ret = new TunaPizza();
    }
    ret->bake();
    ret->deliver();
    return ret;
}

int main() { 
    Pizza *myHawaiiPizza = orderPizza("Hawaii");
    Pizza *myTunaPizza = orderPizza("Tuna");

    delete myHawaiiPizza;
    delete myTunaPizza;
    return 0; 
}

在上面的程式範例中,orderPizza()函式內部依參數來實體化類別,所以當增加口味更改時,修改orderPizza()函式是不可避免的,但我們已經知道會修改甚麼地方,可以將會變動的地方取出並封裝,下面使用PizzaFactory類別來處理披薩的產生,而PizzaStore類別負責披薩產生的後續處理。

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

class Pizza{
public:
    string m_name;
    void bake(){cout << m_name << "進行烘烤" << endl;}
    void delever(){cout << m_name << "進行運送" << endl;}
};
class HawaiiPizza : public Pizza{
public:
    HawaiiPizza(){m_name="Hawaii pizza";}
};
class TunaPizza : public Pizza{
public:
    TunaPizza(){m_name="Tuna pizza";}
};

class PizzaFactory{
public:
    Pizza *createPizza(string name);
};
Pizza *PizzaFactory::createPizza(string name){
    Pizza *ret;
    if(name=="Hawaii"){
        ret = new HawaiiPizza();
    }
    if(name=="Tuna"){
        ret = new TunaPizza();
    }
    return ret;
}

class PizzaStore{
private:
    PizzaFactory m_PizzaFactory;
public:
    PizzaStore(PizzaFactory factory){m_PizzaFactory=factory;}
    Pizza *orderPizza(string name);
};
Pizza *PizzaStore::orderPizza(string name){
    Pizza *ret = m_PizzaFactory.createPizza(name);
    ret->bake();
    ret->deliver();
    return ret;
}

int main() { 
    PizzaFactory myPizzaFactory;
    PizzaStore myPizzaStore(myPizzaFactory);
    Pizza *myHawaiiPizza = myPizzaStore.orderPizza("Hawaii");
    Pizza *myTunaPizza = myPizzaStore.orderPizza("Tuna");

    delete myHawaiiPizza;
    delete myTunaPizza;
    return 0; 
}

上述例子用單一工廠,產生所有的可能類別實例,下面用類似的方式,但是讓每個產品都有各自的工廠,當產品有新增時,上述例子必須要修改工廠方法,下面方法則是增加一個新的工廠子類別,如此較符合開放封閉原則,但有會產生大量工廠子類別的缺點。

我們用HawaiiPizzaFactory和TunaPizzaFactory類別取代原本的PizzaFactory的功能,以下為程式碼。

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

class Pizza{
public:
    string m_name;
    void bake(){cout << m_name << "進行烘烤" << endl;}
    void delever(){cout << m_name << "進行運送" << endl;}
};
class HawaiiPizza : public Pizza{
public:
    HawaiiPizza(){m_name="Hawaii pizza";}
};
class TunaPizza : public Pizza{
public:
    TunaPizza(){m_name="Tuna pizza";}
};

class PizzaFactory{
public:
    virtual Pizza *createPizza() = 0;
};
class HawaiiPizzaFactory : public PizzaFactory{
public:
    Pizza *createPizza();
};
class TunaPizzaFactory : public PizzaFactory{
public:
    Pizza *createPizza();
};
Pizza *HawaiiPizzaFactory::createPizza(){
    Pizza *ret = new HawaiiPizza();
    return ret;
}
Pizza *TunaPizzaFactory::createPizza(){
    Pizza *ret = new TunaPizza();
    return ret;
}
class PizzaStore{
private:
    PizzaFactory *m_PizzaFactory;
public:
    PizzaStore(PizzaFactory *factory){m_PizzaFactory=factory;}
    void setFlavor(PizzaFactory *factory){m_PizzaFactory=factory;}
    Pizza *orderPizza();
};
Pizza *PizzaStore::orderPizza(){
    Pizza *ret = m_PizzaFactory->createPizza();
    ret->bake();
    ret->ship();
    return ret;
}

int main() { 
    HawaiiPizzaFactory myHawaiiFactory;
    PizzaStore myPizzaStore(&myHawaiiFactory);
    Pizza *myHawaiiPizza = myPizzaStore.orderPizza();

    TunaPizzaFactory myTunaFactory;
    myPizzaStore.setFlavor(&myTunaFactory);
    Pizza *myTunaPizza = myPizzaStore.orderPizza();

    delete myHawaiiPizza;
    delete myTunaPizza;
    return 0; 
}