Fork me on GitHub

Programming Design Notes

Design Pattern - Strategy

| Comments

近日拿出 Design Pattern 的書重溫,順便和大家分享一下。

Strategy Pattern 主要的目的是將一些有很大機會轉變的行為或演算法封裝起來,並抽出來, 令到在執行期間也可以替換。這樣做的好處是不用變動核心程式碼,而又做到變更行為等等功能。

以下的例子可能令你更明白這個模式的用途。


你的公司要你開發一個遊戲,這個遊戲是一個古代戰爭遊戲,每個人扮演一個特定角色。 公司希望可以隨時增加角色,你一開始的設計可能會這樣:

(Character.java) download
public abstract class Character {

    private String name;
    private int hp;
    private int mp;

    public Character(String name, int hp, int mp) {
        this.name = name;
        this.hp = hp;
        this.mp = mp;
    }

    public void attack() {
        System.out.println("Attack");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHp() {
        return hp;
    }

    public void setHp(int hp) {
        this.hp = hp;
    }

    public int getMp() {
        return mp;
    }

    public void setMp(int mp) {
        this.mp = mp;
    }

}
(Human.java) download
public class Human extends Character {

    public Human(String name) {
        super(name, 100, 100);
    }

}
(Dwarve.java) download
public class Dwarve extends Character {

    public Dwarve(String name) {
        super(name, 150, 50);
    }

}

這樣的設計原本符合公司規格,隨時可以新增角色,亦利用了 OO 的特性。


公司高層認為每個角色的攻擊都一樣,太乏味了,建議你改一改程式去令每一個角色都拿不同武器去攻擊。 你可能會更改程式成為這樣:

(Character.java) download
public abstract class Character {

    private String name;
    private int hp;
    private int mp;

    public Character(String name, int hp, int mp) {
        this.name = name;
        this.hp = hp;
        this.mp = mp;
    }

    public abstract void attack();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHp() {
        return hp;
    }

    public void setHp(int hp) {
        this.hp = hp;
    }

    public int getMp() {
        return mp;
    }

    public void setMp(int mp) {
        this.mp = mp;
    }

}
(Human.java) download
public class Human extends Character {

    public Human(String name) {
        super(name, 100, 100);
    }

    @Override
    public void attack() {
        System.out.println("Sword attack");
    }

}
(Dwarve.java) download
public class Dwarve extends Character {

    public Dwarve(String name) {
        super(name, 150, 50);
    }

    @Override
    public void attack() {
        System.out.println("Axe attack");
    }

}

Character 內的 attack() 改為 abstract ,每個角色自己實現自己的攻擊模式。 這樣看好像沒有什麼問題,但如果以後增加角色,而這個角色和現有角色的武器一樣,那不是要將程式碼複製出來? 同樣的程式碼可能會在不同角色內出現,以後維護起來便加倍困難。複製一次程式碼 = 連 Bug 也一起複製了。


公司高層又想改一下遊戲,今次改動是可以令角色選擇不同武器。 上次的程式是不容許角色更換武器,因為在 attack() 內限制了角色使用的武器。 今次需要使用 Strategy Pattern 令程式更具彈性:

(Character.java) download
public abstract class Character {

    private String name;
    private int hp;
    private int mp;

    private AttackBehavior attackBehavior;

    public Character(String name, int hp, int mp, AttackBehavior attackBehavior) {
        this.name = name;
        this.hp = hp;
        this.mp = mp;

        this.attackBehavior = attackBehavior;
    }

    public void attack() {
        attackBehavior.attack();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHp() {
        return hp;
    }

    public void setHp(int hp) {
        this.hp = hp;
    }

    public int getMp() {
        return mp;
    }

    public void setMp(int mp) {
        this.mp = mp;
    }

    public AttackBehavior getAttackBehavior() {
        return attackBehavior;
    }

    public void setAttackBehavior(AttackBehavior attackBehavior) {
        this.attackBehavior = attackBehavior;
    }

}
(Human.java) download
public class Human extends Character {

    public Human(String name, AttackBehavior attackBehavior) {
        super(name, 100, 100, attackBehavior);
    }

}
(Dwarve.java) download
public class Dwarve extends Character {

    public Dwarve(String name, AttackBehavior attackBehavior) {
        super(name, 150, 50, attackBehavior);
    }

}
(AttackBehavior.java) download
public interface AttackBehavior {

    public void attack();

}
(SwordAttackBehavior.java) download
public class SwordAttackBehavior implements AttackBehavior {

    @Override
    public void attack() {
        System.out.println("Sword attack");
    }

}
(AxeAttackBehavior.java) download
public class AxeAttackBehavior implements AttackBehavior {

    @Override
    public void attack() {
        System.out.println("Axe attack");
    }

}
(Main.java) download
public class Main {

    public static void main(String[] args) {
        Character character1 = new Human("Lawrence", new AxeAttackBehavior());
        Character character2 = new Human("Tony", new SwordAttackBehavior());

        character1.attack();
        character2.attack();

        character1.setAttackBehavior(new SwordAttackBehavior());
        character1.attack();
    }

}

現在只需要在建立角色時將攻擊行為也一起加到角色中就可以了,而且可以隨時更改攻擊行為。


以下的圖片是描述 Strategy Pattern 的 Class Diagram。(From Wiki)