話說

今天去一間滿想去的公司面試,結果就被電了

有些東西沒有很熟答得很爛,鬱悶了一個下午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()了

 

個人覺得這個模式滿有趣的

也說明了在某些情況下繼承不是萬能的啊

學習學習~

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 頁頁頁六滴 的頭像
    頁頁頁六滴

    人森很精彩,所以要把所有事情都記起來=ˇ=

    頁頁頁六滴 發表在 痞客邦 留言(0) 人氣()