C++設計模式 | 四種行為型模式——模版方法模式、策略模式、命令模式、觀察者模式

模版方法模式

定義一個操作中算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。

AbstractClass(抽象類):在抽象類中定義了一系列基本操作,這些基本操作可以是具體的,也可以是抽象的,每一個基本操作對應算法的一個步驟,在其子類中可以重定義或實現這些步驟。同時,在抽象類中實現了一個模板方法(Template Method),用于定義一個算法的框架,模板方法不僅可以調用在抽象類中實現的基本方法,也可以調用在抽象類的子類中實現的基本方法,還可以調用其他對象中的方法。

ConcreteClass(具體子類):它是抽象類的子類,用于實現在父類中聲明的抽象基本操作以完成子類特定算法的步驟,也可以覆蓋在父類中已經實現的具體基本操作。

#include<iostream>
using namespace std;


class DrinkTemplate{
public:
    //煮水
    virtual void BoildWater() = 0;
    //沖泡
    virtual void Brew() = 0;
    //倒入杯中
    virtual void PourInCup() = 0;
    //加輔助料
    virtual void AddSomething() = 0;

    //模板方法
    void Make(){
        BoildWater();
        Brew();
        PourInCup();
        AddSomething();
    }
};

//沖泡咖啡
class Coffee : public DrinkTemplate{
public:
    virtual void BoildWater(){
        cout << "煮山泉水..." << endl;
    }
    //沖泡
    virtual void Brew(){
        cout << "沖泡咖啡..." << endl;
    }
    //倒入杯中
    virtual void PourInCup(){
        cout << "咖啡倒入杯中..." << endl;
    }
    //加輔助料
    virtual void AddSomething(){
        cout << "加糖,加牛奶,加點醋..." << endl;
    }
};

//沖泡茶水
class Tea : public DrinkTemplate{
public:
    virtual void BoildWater(){
        cout << "煮自來水..." << endl;
    }
    //沖泡
    virtual void Brew(){
        cout << "沖泡鐵觀音..." << endl;
    }
    //倒入杯中
    virtual void PourInCup(){
        cout << "茶水倒入杯中..." << endl;
    }
    //加輔助料
    virtual void AddSomething(){
        cout << "加糖..加檸檬...加生姜..." << endl;
    }
};

void test01(){

    Tea* tea = new Tea;
    tea->Make();

    cout << "-----------" << endl;

    Coffee* coffee = new Coffee;
    coffee->Make();
    
}


int main(){

    test01();

    return 0;
}
模板方法的優缺點及適用場景

優點:
(1)在父類中形式化地定義一個算法,而由它的子類來實現細節的處理,在子類實現詳細的處理算法時并不會改變算法中步驟的執行次序。
(2)模板方法模式是一種代碼復用技術,它在類庫設計中尤為重要,它提取了類庫中的公共行為,將公共行為放在父類中,而通過其子類來實現不同的行為,它鼓勵我們恰當使用繼承來實現代碼復用。
(3)可實現一種反向控制結構,通過子類覆蓋父類的鉤子方法來決定某一特定步驟是否需要執行。
(4)在模板方法模式中可以通過子類來覆蓋父類的基本方法,不同的子類可以提供基本方法的不同實現,更換和增加新的子類很方便,符合單一職責原則和開閉原則。

缺點:
需要為每一個基本方法的不同實現提供一個子類,如果父類中可變的基本方法太多,將會導致類的個數增加,系統更加龐大,設計也更加抽象。

適用場景:
(1)具有統一的操作步驟或操作過程;
(2) 具有不同的操作細節;
(3) 存在多個具有同樣操作步驟的應用場景,但某些具體的操作細節卻各不相同;

在抽象類中統一操作步驟,并規定好接口;讓子類實現接口。這樣可以把各個具體的子類和操作步驟解耦合。

策略模式

策略模式定義了一系列的算法,并將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法獨立于使用它的客戶而獨立變化。

Context(環境類):環境類是使用算法的角色,它在解決某個問題(即實現某個方法)時可以采用多種策略。在環境類中維持一個對抽象策略類的引用實例,用于定義所采用的策略。
Strategy(抽象策略類):它為所支持的算法聲明了抽象方法,是所有策略類的父類,它可以是抽象類或具體類,也可以是接口。環境類通過抽象策略類中聲明的方法在運行時調用具體策略類中實現的算法。
ConcreteStrategy(具體策略類):它實現了在抽象策略類中聲明的算法,在運行時,具體策略類將覆蓋在環境類中定義的抽象策略類對象,使用一種具體的算法實現某個業務處理。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;


//抽象武器 武器策略
class WeaponStrategy{
public:
    virtual void UseWeapon() = 0;
};

class Knife : public WeaponStrategy{
public:
    virtual void UseWeapon(){
        cout << "使用匕首!" << endl;
    }
};

class AK47 :public WeaponStrategy{
public:
    virtual void UseWeapon(){
        cout << "使用AK47!" << endl;
    }
};

class Character {
public:
    void setWeapon(WeaponStrategy* weapon){
        this->pWeapon = weapon;
    }
    void ThrowWeapon(){
        this->pWeapon->UseWeapon();
    }
public:
    WeaponStrategy* pWeapon;
};


void test01(){
    
    //創建角色
    Character* character = new Character;

    //武器策略
    WeaponStrategy* knife = new Knife;
    WeaponStrategy* ak47 = new AK47;

    character->setWeapon(knife);
    character->ThrowWeapon();

    character->setWeapon(ak47);
    character->ThrowWeapon();

    delete ak47;
    delete knife;
    delete character;
}

int main(void){

    test01();
    
    return 0;
}
策略模式的優缺點及適用場景

優點:
(1)策略模式提供了對“開閉原則”的完美支持,用戶可以在不修改原有系統的基礎上選擇算法或行為,也可以靈活地增加新的算法或行為。
(2)使用策略模式可以避免多重條件選擇語句。多重條件選擇語句不易維護,它把采取哪一種算法或行為的邏輯與算法或行為本身的實現邏輯混合在一起,將它們全部硬編碼(Hard Coding)在一個龐大的多重條件選擇語句中,比直接繼承環境類的辦法還要原始和落后。
(3)策略模式提供了一種算法的復用機制。由于將算法單獨提取出來封裝在策略類中,因此不同的環境類可以方便地復用這些策略類。

缺點:
(1)客戶端必須知道所有的策略類,并自行決定使用哪一個策略類。這就意味著客戶端必須理解這些算法的區別,以便適時選擇恰當的算法。換言之,策略模式只適用于客戶端知道所有的算法或行為的情況。
(2)策略模式將造成系統產生很多具體策略類,任何細小的變化都將導致系統要增加一個新的具體策略類。

適用場景:
準備一組算法,并將每一個算法封裝起來,使得它們可以互換。

命令模式

    將一個請求封裝為一個對象,從而讓我們可用不同的請求對客戶進行參數化;對請求排隊或者記錄請求日志,以及支持可撤銷的操作。命令模式是一種對象行為型模式,其別名為動作(Action)模式或事務(Transaction)模式。
命令模式可以將請求發送者和接收者完全解耦,發送者與接收者之間沒有直接引用關系,發送請求的對象只需要知道如何發送請求,而不必知道如何完成請求。

Command(抽象命令類):抽象命令類一般是一個抽象類或接口,在其中聲明了用于執行請求的execute()等方法,通過這些方法可以調用請求接收者的相關操作。

ConcreteCommand(具體命令類):具體命令類是抽象命令類的子類,實現了在抽象命令類中聲明的方法,它對應具體的接收者對象,將接收者對象的動作綁定其中。在實現execute()方法時,將調用接收者對象的相關操作(Action)。

Invoker(調用者):調用者即請求發送者,它通過命令對象來執行請求。一個調用者并不需要在設計時確定其接收者,因此它只與抽象命令類之間存在關聯關系。在程序運行時可以將一個具體命令對象注入其中,再調用具體命令對象的execute()方法,從而實現間接調用請求接收者的相關操作。

Receiver(接收者):接收者執行與請求相關的操作,它具體實現對請求的業務處理。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<queue>

using namespace std;


//協議處理類
class HandleClientProtocol{
public:
    //處理增加金幣
    void AddMoney(){
        cout << "給玩家增加金幣!" << endl;
    }

    //處理增加鉆石
    void AddDiamond(){
        cout << "給玩家增加鉆石!" << endl;
    }

    //處理玩家裝備
    void AddEquipment(){
        cout << "給玩家穿裝備!" << endl;
    }

    //處理玩家升級
    void addLevel(){
        cout << "給玩家升級!" << endl;
    }
};

//命令接口
class AbstractCommand{
public:
    virtual void handle() = 0; //處理客戶端請求的接口
};


//處理增加金幣請求
class AddMoneyCommand :public AbstractCommand{
public:
    AddMoneyCommand(HandleClientProtocol* protocol){
        this->pProtocol = protocol;
    }
    virtual void handle(){
        this->pProtocol->AddMoney();
    }
public:
    HandleClientProtocol* pProtocol;
};

//處理增加鉆石的請求
class AddDiamondCommand :public AbstractCommand{
public:
    AddDiamondCommand(HandleClientProtocol* protocol){
        this->pProtocol = protocol;
    }
    virtual void handle(){
        this->pProtocol->AddDiamond();
    }
public:
    HandleClientProtocol* pProtocol;
};


//處理玩家穿裝備的請求
class AddEquipmentCommand : public AbstractCommand{
public:
    AddEquipmentCommand(HandleClientProtocol* protocol){
        this->pProtocol = protocol;
    }
    virtual void handle(){
        this->pProtocol->AddEquipment();
    }
public:
    HandleClientProtocol* pProtocol;
};

//處理玩家升級的請求
class AddLevelCommand : public AbstractCommand{
public:
    AddLevelCommand(HandleClientProtocol* protocol){
        this->pProtocol = protocol;
    }
    virtual void handle(){
        this->pProtocol->addLevel();
    }
public:
    HandleClientProtocol* pProtocol;

};


//服務器程序
class Serser{
public:
    void addRequest(AbstractCommand* command){
        mCommands.push(command);
    }

    void startHandle(){
        while (!mCommands.empty()){

            AbstractCommand* command = mCommands.front();
            command->handle();
            mCommands.pop();
        }
    }
public:
    queue<AbstractCommand*> mCommands;
};

void test01(){
    
    HandleClientProtocol* protocol = new HandleClientProtocol;
    //客戶端增加金幣的請求
    AbstractCommand* addmoney = new AddMoneyCommand(protocol);
    //客戶端增加鉆石的請求
    AbstractCommand* adddiamond = new AddDiamondCommand(protocol);
    //客戶端穿裝備的請求
    AbstractCommand* addequpment = new AddEquipmentCommand(protocol);
    //客戶端升級請求
    AbstractCommand* addlevel = new AddLevelCommand(protocol);

    Serser* server = new Serser;
    //將客戶端請求加入到處理的隊列中
    server->addRequest(addmoney);
    server->addRequest(adddiamond);
    server->addRequest(addequpment);
    server->addRequest(addlevel);

    //服務器開始處理請求
    server->startHandle();
}

int main(void){
    test01();
    return 0;
}
命令模式的優缺點及適用場景

優點:
(1)降低系統的耦合度。由于請求者與接收者之間不存在直接引用,因此請求者與接收者之間實現完全解耦,相同的請求者可以對應不同的接收者,同樣,相同的接收者也可以供不同的請求者使用,兩者之間具有良好的獨立性。
(2)新的命令可以很容易地加入到系統中。由于增加新的具體命令類不會影響到其他類,因此增加新的具體命令類很容易,無須修改原有系統源代碼,甚至客戶類代碼,滿足“開閉原則”的要求。
(3)可以比較容易地設計一個命令隊列或宏命令(組合命令)。

缺點:
使用命令模式可能會導致某些系統有過多的具體命令類。因為針對每一個對請求接收者的調用操作都需要設計一個具體命令類,因此在某些系統中可能需要提供大量的具體命令類,這將影響命令模式的使用。

適用場景:
(1) 系統需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。請求調用者無須知道接收者的存在,也無須知道接收者是誰,接收者也無須關心何時被調用。
(2) 系統需要在不同的時間指定請求、將請求排隊和執行請求。一個命令對象和請求的初始調用者可以有不同的生命期,換言之,最初的請求發出者可能已經不在了,而命令對象本身仍然是活動的,可以通過該命令對象去調用請求接收者,而無須關心請求調用者的存在性,可以通過請求日志文件等機制來具體實現。
(3) 系統需要將一組操作組合在一起形成宏命令。

觀察者模式

觀察者模式是用于建立一種對象與對象之間的依賴關系,一個對象發生改變時將自動通知其他對象,其他對象將相應作出反應。在觀察者模式中,發生改變的對象稱為觀察目標,而被通知的對象稱為觀察者,一個觀察目標可以對應多個觀察者,而且這些觀察者之間可以沒有任何相互聯系,可以根據需要增加和刪除觀察者,使得系統更易于擴展。

Subject(被觀察者或目標,抽象主題):被觀察的對象。當需要被觀察的狀態發生變化時,需要通知隊列中所有觀察者對象。Subject需要維持(添加,刪除,通知)一個觀察者對象的隊列列表。
ConcreteSubject(具體被觀察者或目標,具體主題):被觀察者的具體實現。包含一些基本的屬性狀態及其他操作。
Observer(觀察者):接口或抽象類。當Subject的狀態發生變化時,Observer對象將通過一個callback函數得到通知。
ConcreteObserver(具體觀察者):觀察者的具體實現。得到通知后將完成一些具體的業務邏輯處理。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<list>
using namespace std;

//抽象的英雄 抽象的觀察者
class AbstractHero{
public:
    virtual void Update() = 0;
};


//具體英雄  具體觀察者
class HeroA :public AbstractHero{
public:
    HeroA(){
        cout << "英雄A正在擼BOSS ..." << endl;
    }
    virtual void Update(){
        cout << "英雄A停止擼,待機狀態..." << endl;
    }
};

class HeroB :public AbstractHero{
public:
    HeroB(){
        cout << "英雄B正在擼BOSS ..." << endl;
    }
    virtual void Update(){
        cout << "英雄B停止擼,待機狀態..." << endl;
    }
};

class HeroC :public AbstractHero{
public:
    HeroC(){
        cout << "英雄C正在擼BOSS ..." << endl;
    }
    virtual void Update(){
        cout << "英雄C停止擼,待機狀態..." << endl;
    }
};

class HeroD :public AbstractHero{
public:
    HeroD(){
        cout << "英雄D正在擼BOSS ..." << endl;
    }
    virtual void Update(){
        cout << "英雄D停止擼,待機狀態..." << endl;
    }
};

class HeroE :public AbstractHero{
public:
    HeroE(){
        cout << "英雄E正在擼BOSS ..." << endl;
    }
    virtual void Update(){
        cout << "英雄E停止擼,待機狀態..." << endl;
    }
};


//觀察目標抽象
class AbstractBoss{
public:
    //添加觀察者
    virtual void addHero(AbstractHero* hero) = 0;
    //刪除觀察者
    virtual void deleteHero(AbstractHero* hero) = 0;
    //通知所有觀察者
    virtual void notify() = 0; 
};


//具體的觀察者 BOSSA
class BOSSA : public AbstractBoss{
public:
    virtual void addHero(AbstractHero* hero){
        pHeroList.push_back(hero);
    }
    //刪除觀察者
    virtual void deleteHero(AbstractHero* hero){
        pHeroList.remove(hero);
    }
    //通知所有觀察者
    virtual void notify(){
        for (list<AbstractHero*>::iterator it = pHeroList.begin(); it != pHeroList.end();it ++){
            (*it)->Update();
        }
    }
public:
    list<AbstractHero*> pHeroList;
};



void test01(){
    

    //創建觀察者
    AbstractHero* heroA = new HeroA;
    AbstractHero* heroB = new HeroB;
    AbstractHero* heroC = new HeroC;
    AbstractHero* heroD = new HeroD;
    AbstractHero* heroE = new HeroE;

    //創建觀察目標
    AbstractBoss* bossA = new BOSSA;
    bossA->addHero(heroA);
    bossA->addHero(heroB);
    bossA->addHero(heroC);
    bossA->addHero(heroD);
    bossA->addHero(heroE);


    cout << "heroC陣亡..." << endl;
    bossA->deleteHero(heroC);

    cout << "Boss死了...通知其他英雄停止攻擊,搶裝備..." << endl;
    bossA->notify();

}


int main(void){
    test01();
    return 0;
}
觀察者模式的優缺點及適用場景

優點:
(1)觀察者模式可以實現表示層和數據邏輯層的分離,定義了穩定的消息更新傳遞機制,并抽象了更新接口,使得可以有各種各樣不同的表示層充當具體觀察者角色。
(2)觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合。觀察目標只需要維持一個抽象觀察者的集合,無須了解其具體觀察者。由于觀察目標和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。
(3)觀察者模式支持廣播通信,觀察目標會向所有已注冊的觀察者對象發送通知,簡化了一對多系統設計的難度。
(4)觀察者模式滿足“開閉原則”的要求,增加新的具體觀察者無須修改原有系統代碼,在具體觀察者與觀察目標之間不存在關聯關系的情況下,增加新的觀察目標也很方便。

缺點:
(1)如果一個觀察目標對象有很多直接和間接觀察者,將所有的觀察者都通知到會花費很多時間。
(2)觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。

適用場景:
(1)一個抽象模型有兩個方面,其中一個方面依賴于另一個方面,將這兩個方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。
(2)一個對象的改變將導致一個或多個其他對象也發生改變,而并不知道具體有多少對象將發生改變,也不知道這些對象是誰。
(3)需要在系統中創建一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 下面總結設計模式中的行為型模式: 1.責任鏈模式 顧名思義,責任鏈模式(Chain of Responsibili...
    Steven1997閱讀 3,477評論 0 1
  • 行為型模式用于描述程序在運行時復雜的流程控制,即描述多個類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任...
    jiangmo閱讀 774評論 0 1
  • javascript設計模式與開發實踐 設計模式 每個設計模式我們需要從三點問題入手: 定義 作用 用法與實現 單...
    穿牛仔褲的蚊子閱讀 4,081評論 0 13
  • 用來對類或對象怎樣交互和怎樣分配職責進行描述 1.模板方法2.命令模式3.策略模式4.觀察者模式 1.模板方法 1...
    木魚_cc閱讀 664評論 0 0
  • 這里是對《設計模式Java版》[https://gof.quanke.name]的提煉匯總,在真正深入理解之前,方...
    LeonXtp閱讀 1,026評論 0 0