話說
今天去一間滿想去的公司面試,結果就被電了
有些東西沒有很熟答得很爛,鬱悶了一個下午XD
看來想去的公司就是比較難進啊~ 繼續努力吧~
今天有被問到設計模式,我也答不出幾個XD
所以就來記錄個設計模式吧~ 今天要寫的是策略模式~
所謂策略模式呢,就是針對"策略"去做不同的設計
策略在這邊就是表示"執行的方法"
具體來說就是把物件的行為分出來實作,在依照物件需要的行為傳行為進去
舉個例吧,假設今天要開發一個遊戲,裡面有很多的職業
裡面有射手、補師跟戰士三種職業
照一般的思維,可能會想說建一個基底類別叫charcater
然後可能有attcak()、move()、defend()之類的,再讓繼承charcater的class去實作裡面的功能
看起來很美好啊,有什麼問題呢
現在只有三種職業還好處理,如果以後有一堆職業的話就有點難辦了
比如說現在有一堆職業,裡面只有補師可以補血
此時charcater裡必須要有一個介面叫做heal(),但每個職業都要實作heal(),只是除了補師之外實作的內容都是空的
這樣就顯得非常奇怪
又或是有些職業的attack()內容都一樣,卻要在每個職業裡面都實作一樣的內容,明顯就是重複一堆一樣的code,相當的不好...
於是呢面對這種情況,策略模式就可以派上用場了
在Head First Design Pattern 此書中策略模式的原則如下:
定義演算法家族,個別封裝起來,讓他們之間可以互相替換,此模式讓演算法的變動獨立於使用操作的物件之外
然後原則如下:
1. 將演算法中可能的變化獨立出來
2. 針對介面寫程式而非針對實踐去編程
3. 多用合成少用繼承
在我的理解就是把物件的行為獨立出來和物件分開,減少物件和他的行為的黏稠度
然後看到合成這個關鍵字了吧,我覺得這是這個模式的關鍵
因為繼承雖然很好用,但在這個情況下卻不是很好的做法,所以用合成的方式比較適當
這邊拿wiki的code來用一下,以下是實作的一個範例:
#include <iostream> using namespace std; class StrategyInterface { public: virtual void execute() = 0; }; class ConcreteStrategyA: public StrategyInterface { public: virtual void execute() { cout << "Called ConcreteStrategyA execute method" << endl; } }; class ConcreteStrategyB: public StrategyInterface { public: virtual void execute() { cout << "Called ConcreteStrategyB execute method" << endl; } }; class ConcreteStrategyC: public StrategyInterface { public: virtual void execute() { cout << "Called ConcreteStrategyC execute method" << endl; } }; class Context { private: StrategyInterface *_strategy; public: Context(StrategyInterface *strategy):_strategy(strategy) { } void set_strategy(StrategyInterface *strategy) { _strategy = strategy; } void execute() { _strategy->execute(); } }; int main(int argc, char *argv[]) { ConcreteStrategyA concreteStrategyA; ConcreteStrategyB concreteStrategyB; ConcreteStrategyC concreteStrategyC; Context contextA(&concreteStrategyA); Context contextB(&concreteStrategyB); Context contextC(&concreteStrategyC); contextA.execute(); contextB.execute(); contextC.execute(); contextA.set_strategy(&concreteStrategyB); contextA.execute(); contextA.set_strategy(&concreteStrategyC); contextA.execute(); return 0; }
這個例子應該滿好理解的吧
如果要用剛剛遊戲開發的例子來看,大概可以這麼做:
class AttackBehavior {
virtual void attack() = 0; // attack行為的介面
};
class MoveBehavior {
virtual void move() = 0; // move行為的介面
};
class NormalAttack : public AttackBehavior {
void attack() { cout << "normal attack" << endl; } // 實作attack的內容
};
class NormalAttack : public MoveBehavior {
void move() { cout << "normak move" << endl; } // 實作move的內容
};
// 角色的介面
class character {
protected:
AttackBehavior *m_attack;
MoveBehavior "m_move;
public:
character(AttackBehavior* atk, MoveBehavior* move) : m_attack(atk), m_move(move) { } // 由建構時傳入動作
void PerformAttack() { m_attack->attack(); }
void PerformMove() { m_move->move(); }
void SetAttackMode(AttackBehavior* atk) { m_attack = atk; } // 設定動作的function
void SetMoveMode(MoveBehavior* move) { m_move = move; }
};
// 再來是角色的實作
class warrior : public charater {
public:
warrior (AttackBehavior* atk, MoveBehavior* move) : charater(atk, move) { }
};
class archer : public charater {
public:
archer (AttackBehavior* atk, MoveBehavior* move) : charater(atk, move) { }
};
實際運作的時候,就是先new出行為來,然後作為參數傳到角色裡面,大概是這樣:
AttackBehavior *attack = new NormalAttack();
MoveBehavior *move = new NormalMove();
warrior *warrior_1 = new warrior(attack, move);
這樣當warrior_1在PerformAttack()的時候,就會去執行NormalAttack裡的attack()了
個人覺得這個模式滿有趣的
也說明了在某些情況下繼承不是萬能的啊
學習學習~
留言列表