策略模式(Strategy Pattern)

策略模式(Strategy Pattern)

假設你已經創造一個遊戲

裡面有一個角色是一個劍士

class SwordsMan{
    private String name;
    public SwordsMan(String userName){
        name = userName;
    }
    public void run(){
        System.out.println(name + " 跑走");
    }
    public void fight(){
        System.out.println(name + " 揮舞劍");
    }
}


裡面又有一個角色是魔法師

class Magician {
    private String name;
    public Magician(String userName){
        name = userName;
    }
    public void run(){
        System.out.println(name + " 跑走");
    }
    public void fight(){
        System.out.println(name + " 魔法攻擊");
    }
}

這時候你就會想到 每一個角色當中 都有重複的跑方法
這樣好了 我就寫一個方法 然後讓所有的角色來繼承(inheritance)
於是就寫出了這樣的程式

class Character{
    protected String name;
    public Character(String userName){
        name = userName;
    }
    protected void run(){
        System.out.println(name +" 跑走");
    }
    public void fight(){}
}

因為每個角色的攻擊方式都不一樣
然後所有的角色都去推翻fight()這個方法(覆寫)

class SwordsMan extends Character{
    public SwordsMan(String username){
        super(username);
    }
    public void fight(){
        System.out.println(name + " 揮舞劍");
    }
}
class Magician extends Character{
    public Magician(String username){
        super(username);
    }
    public void fight(){
        System.out.println(name + " 魔法攻擊");
    }
}

…其他角色同樣繼承覆寫…

這時候 只要將魔法師 、劍士或其他角色實體化(instance)
就可以使用各自的方法了

可是這樣會出現一個問題,就是程式靈活度不夠,
想看看今天我有50個角色,裡面有20個角色攻擊都是揮舞劍,
這樣我每個角色都要去覆寫fight()不就累死了,

再說,如果我要修改每一個揮舞劍的角色改成揮舞刀,
那這樣也是會浪費很多時間,
所以我們就必須要把常常會變動的部分取出來,

所以我們將fight()拿出來寫成介面(interface)

interface FightMethod{
    public void fight(String name);
}

然後我們用SwordsMan這個類別去繼承它嗎?
不!

我們已經繼承Character, Java不允許多重繼承
所以我們實作(implement)它嗎?
不!這樣跟剛剛覆寫fight()方法不就一樣的問題?
複合才是我們要的答案!

我們定義一個使用劍的類別去實做它

class UseSword implements FightMethod{
    public void fight(String name){
        System.out.println(name + " 揮舞劍");
    }
}

然後在劍士的類別改成這樣

class SwordsMan extends Character{
    private UseSword charUseSword;
    public SwordsMan(String username){
        super(username);
        charUseSword = new UseSword();
        charUseSword.fight(username);
    }
}

這樣寫很有問題! 什麼問題?

回想剛剛的問題,又在這裡發生了,這樣每一個角色都必須宣告一個攻擊的物件,
並且配置實體給它,那100個角色仍然要配置100個實體,
你會累死…

怎麼辦呢?

多型就是用在這個地方
我們這樣宣告

FightMethod fm = new UseSword();
fm.fight();

FightMethod fm是形式型態的宣告,而
new UseSword()才是你實際型態的宣告。

關於多型(Polymorphism),「當某變數的實際型態(actual type)和形式型態
(formal type)不一致時,呼叫此變數的 method,一定會呼叫到「正確」的版本,
也就是實際型態的版本。

所以我們這樣就可以在Character類別直接這樣宣告

class Character{
    public String name;
    protected FightMethod fm;
    public Character(String userName){
       name =  userName;
    }
    public void run(){
        System.out.println(name +" 跑走");
    }
    protected void useWeapon(){
        fm.fight(name);
    }
}

而在SwordsMan內直接這樣宣告

class SwordsMan extends Character{
    public SwordsMan(String username){
        super(username);
        fm = new UseSword();
    }   
}

這樣劍士要改變攻擊方式就可以直接換成useKnife();
或者要增加一百個角色,也可以直接在介面上增加攻擊方式。

完整程式碼

class Character{
    protected String name;
    protected FightMethod fm;
    public Character(String userName){
       name =  userName;
    }
    public void run(){
        System.out.println(name +" 跑走");
    }
    protected void useWeapon(){
        fm.fight(name);
    }
}
interface FightMethod{
    public void fight(String name);
}
class UseSword implements FightMethod{
    public void fight(String name){
        System.out.println(name+" 揮舞劍");
    }
}
class UseMagic implements FightMethod{
    public void fight(String name){
        System.out.println(name+" 使用魔法");
    }
}
class SwordsMan extends Character{
    public SwordsMan(String username){
        super(username);
        fm = new UseSword();
    }

}
class Magician extends Character{
    public Magician(String username){
        super(username);
        fm = new UseMagic();
    }

}
class TestJava { 
    public static void main(String[] args){
        SwordsMan swordMan = new SwordsMan("John");
        Magician magician = new Magician("Mary");
        swordMan.run();
        magician.useWeapon();
        swordMan.useWeapon();
    }
}

無論是魔法師或者劍士, 都會自動對應到自己應該對應的方法

John 跑走
Mary 使用魔法
John 揮舞劍

你看出多型的好處了嗎?

參考:

深入淺出JAVA(book)
深入淺出設計模式
歐萊禮網頁http://www.oreilly.com.tw/column_sleepless.php?id=j022
良葛格http://caterpillar.onlyfun.net/Gossip/JavaEssence/InheritanceWhat.html