趣谈设计模式 | 策略模式(Strategy):你还在使用冗长的if-else吗?
文章目錄
- 案例:指揮官AI
- 策略模式
- 配合工廠模式
- 總結(jié)
- 完整代碼與文檔
案例:指揮官AI
案例可能不符合實際邏輯,僅用于表述設(shè)計模式的思想,勿介意
假設(shè)我們開發(fā)了一款類似全面戰(zhàn)爭的即時戰(zhàn)略游戲,為了能夠增加玩家的對抗體驗,我們決定設(shè)計一個指揮官AI,與以往的無腦人機不同,指揮官會根據(jù)戰(zhàn)況以及玩家的操作,來指定克制玩家的計策。
例如當玩家派出騎兵時,指揮官就會派出槍兵,當玩家退回城中防守時,指揮官就會派出破城槌來攻城。
于是我們初步的設(shè)計如下,我們使用If-else這樣的條件判斷,根據(jù)玩家的不同行為來做出不同的操作。
class Commander {void doAction(const std::string& situation) {if(situation == "wall"){//對付城墻的戰(zhàn)術(shù)}else if(situation == "infantry"){//對付步兵的戰(zhàn)術(shù)}else if(situation == "cavalry"){//對付騎兵的戰(zhàn)術(shù)}......} }如果對于簡單的功能來說,這樣的代碼并沒有什么問題,因為代碼量不多,后續(xù)也不需要拓展和修改。但是對于一款戰(zhàn)略游戲來說,戰(zhàn)場上的形式千變?nèi)f化,兵種、陣法、戰(zhàn)術(shù)等策略可能達到龐大的數(shù)量。如果一個項目中寫上成百上千個條件判斷,那無論是拓展性、維護性、代碼的可讀性都十分的低下,所以我們需要考慮一下別的設(shè)計。
這時,就到了策略模式大顯身手的時候了
策略模式
策略模式就是定義了一套算法族,將它們分別封裝起來,讓它們之間可以互相替換,這樣就能夠輕松的切換不同的算法來解決同一個問題的不同情況,這種模式讓算法的變化獨立于使用算法的客戶端
策略模式由以下三者組成
- Strategy(策略):策略的接口,所有的具體策略都需要實現(xiàn)它
- ConcreteStrategy(具體的策略):具體的策略(如上面的騎兵、步兵、城墻策略)
- Context(上下文):策略的使用者(如上面的指揮官)
類圖如下
下面就使用策略模式,來對代碼進行優(yōu)化
首先定義出Strategy接口,它提供一個getStrategy函數(shù)來幫助指揮官獲取策略的內(nèi)容
class Strategy { public:virtual ~Strategy() = default;virtual void getStrategy() = 0; //獲取策略的內(nèi)容 };接著實現(xiàn)具體策略ConcreteStrategy
class infantryStrategy : public Strategy { public:void getStrategy() override{std::cout << "計策:用弓箭手來對付步兵" << std::endl;} };class wallStrategy : public Strategy { public:void getStrategy() override{std::cout << "計策:用破城槌來破壞城墻" << std::endl;} };class cavalryStrategy : public Strategy { public:void getStrategy() override{std::cout << "計策:長槍兵來對付騎兵" << std::endl;} };因為考慮到程序的拓展,有可能我們后期還會推出各種指揮官的特化版本,因此我們實現(xiàn)一個通用的Context接口,并且下面這種設(shè)計能夠動態(tài)的修改策略
class Context { public:Context(Strategy* strategy = nullptr): _strategy(strategy){}virtual ~Context() = default;virtual void doAction() = 0; //指揮官做出行動virtual void setstrategy(Strategy* strategy) = 0; //變更策略protected:Strategy* _strategy; //策略指針,利用多態(tài)來轉(zhuǎn)變不同的策略 };接著實現(xiàn)我們具體的指揮官
class Commander : public Context {void doAction() override{if(_strategy){_strategy->getStrategy();}}void setstrategy(Strategy* strategy) override{_strategy = strategy;} };寫個程序測試一下,分別讓指揮官執(zhí)行兩種策略
int main() {Context* commander = new Commander(); //指揮官對象Strategy* s1 = new cavalryStrategy; //策略對象Strategy* s2 = new wallStrategy; commander->setstrategy(s1); //當玩家的騎兵到來的時候commander->doAction();commander->setstrategy(s2); //當玩家退回城堡中防守時 commander->doAction();delete s2, s1, commander;return 0; }
通過上面這種方法,我們很簡單的就從If-else中脫身而出,并且如果當我們想要增加新策略的時候,也僅僅只需要實現(xiàn)一個ConcreteStrategy即可,而如果我們想增加新的Strategy接口,如(指揮官自身的行動),我們也只需要在Context中再組合上一個新的Strategy即可。
這樣的代碼不僅符合開放-封閉原則,而且通過對象組合建立的系統(tǒng)還具有很大的彈性。并且這種將算法族封裝成類的方式還支持動態(tài)修改行為。
配合工廠模式
上面代碼還遺留一些小問題,我們在上面是直接指定了具體的某些策略,而通常在使用中我們的程序并不會這樣的指定,而是會通過具體的場景來判斷應(yīng)該創(chuàng)建哪個策略來使用。
所以為了封裝創(chuàng)建邏輯,并且對客戶端代碼屏蔽創(chuàng)建細節(jié),我們可以運用上一篇所提到的工廠模式,將根據(jù)場景創(chuàng)建策略的邏輯放到工廠類中
如果對于工廠模式不了解的可以看看我的往期博客
趣談設(shè)計模式 | 工廠模式(Factory):利用工廠來創(chuàng)建對象
這時候我們只需要輸入需求,就可以通過策略工廠來生成我們需要的策略對象了
int main() {Context* commander = new Commander(); //指揮官對象StrategyFactory factoty; //策略工廠commander->setstrategy(factoty.getStrategyObject("cavalry")); //當玩家的騎兵到來的時候commander->doAction();commander->setstrategy(factoty.getStrategyObject("wall")); //當玩家退回城堡中防守時 commander->doAction();delete commander;return 0; }需要注意的是上面生成的策略對象都是我們提前緩存到策略工廠里的,因此都是無狀態(tài)的對象,里面不能有成員變量。
如果需要保存狀態(tài)的策略對象,我們可以通過查表法和元組來實現(xiàn),由于那種場景并不常見,所以這里就不介紹了。
總結(jié)
要點
- 策略模式由策略、策略接口、上下文三部分組成,主要作用就是用來解耦策略的定義、創(chuàng)建、使用
- 策略模式的核心就是策略的自由切換
- 為了避免策略類對外暴露,通常與工廠模式搭配使用
- 策略模式定義一族算法類,將每個算法分別封裝起來,讓它們可以互相替換。策略模式可以使算法的變化獨立于使用它們的客戶端(代指使用算法的代碼)
應(yīng)用場景
- 如果在一個系統(tǒng)里面有許多類,它們之間的區(qū)別僅在于它們的行為,希望動態(tài)地讓一個對象在許多行為中選擇一種行為時
- 一個決策者需要動態(tài)的在多種策略(算法)中選擇一種時
- 一個對象有很多行為,不想使用冗長的條件判斷語句來決定對象的行為時(即標題說的取代if-else)
完整代碼與文檔
如果有需要完整代碼或者markdown文檔的同學(xué)可以點擊下面的github鏈接
github
總結(jié)
以上是生活随笔為你收集整理的趣谈设计模式 | 策略模式(Strategy):你还在使用冗长的if-else吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 趣谈设计模式 | 工厂模式(Factor
- 下一篇: 趣谈设计模式 | 代理模式(Proxy)