Java設計模式之再從[暗黑破壞神"裝備鑲嵌寶石系統"]分析裝飾(Decorator)模式
阿新 • • 發佈:2018-12-26
在暗黑破壞神等RPG遊戲中,會遇到如下的一些場景:我有一把很普通的武器,我通過給它“注入魔法力量”、“鑲嵌寶石”,來使得它擁有一些攻擊特效:例如,一把普通長劍,鑲嵌紅寶石後,可以附帶火焰傷害,鑲嵌了一個藍寶石後,可以使得長劍攻擊帶有冰凍傷害。
如何實現上述的機制呢?首先我們想到的是繼承,因為,一個鑲嵌了紅寶石、藍寶石的長劍,一定是一個鑲嵌了寶石的長劍,並且一定一把長劍。如此下來,程式的結構會很複雜,而且你可能會想,要是鑲嵌的是別的寶石怎麼辦呢?如果出了寶石外,可以鑲嵌其他物品(如符文),那是不是應該把繼承的結構改變呢?可見,繼承並不是一個最優的方案。
仔細分析一下這種情況,我們會發現,所謂“火焰傷害”、“冰凍傷害”,只是一些“攻擊特效”,說白了就是長劍的攻擊的裝飾。我們僅僅是為了能夠在長劍攻擊的時候,增加一些職責:觸發火焰傷害、觸發冰凍傷害。這個時候我們就可以用到裝飾(Decorator)模式——它的意圖是動態地給一個物件新增一些額外的職責。就增加新功能來說,它比繼承更為靈活。
假設我想給我的長劍賦予眩暈、暴擊和中毒攻擊三種特效,那麼Java程式碼可以這樣寫:
interface Weapon{ void printInfo(); } class Sword implements Weapon { public void printInfo(){ System.out.println ("一把長劍"); } } abstract class WeaponDecorator implements Weapon { public Weapon weapon; public WeaponDecorator(Weapon _weapon){ weapon = _weapon; } public void printInfo(){ weapon.printInfo(); } } class PoisonDecorator extends WeaponDecorator { public PoisonDecorator(Weapon _weapon) { super(_weapon); } public void printInfo() { weapon.printInfo(); //以下是自己新增的方法(裝飾) System.out.println(" 有50%機率造成敵人中毒"); } } class CriticalDecorator extends WeaponDecorator { public CriticalDecorator(Weapon _weapon) { super(_weapon); } public void printInfo() { weapon.printInfo(); //以下是自己新增的方法(裝飾) System.out.println(" 有50%機率造成致命一擊"); } } class DazzleDecorator extends WeaponDecorator { public DazzleDecorator(Weapon _weapon) { super(_weapon); } public void printInfo() { weapon.printInfo(); //以下是自己新增的方法(裝飾) System.out.println(" 有50%機率造成敵人眩暈"); } } class Decorator { public static void main(String[] args) { Weapon superWeapon = new DazzleDecorator(new CriticalDecorator(new PoisonDecorator(new Sword()))); superWeapon.printInfo(); } }
武器都繼承於Weapon介面,此介面聲明瞭一個方法:printInfo(),將武器的資訊打印出來。我們的裝飾器類WeaponDecorator中,包含了一個原始的Weapon物件引用,接下來是重點:當呼叫裝飾器類的printInfo時,它會先呼叫Weapon物件的printInfo,然後再呼叫自己額外增加的一些程式碼。當然,呼叫Weapon.printInfo的順序可先可後,但是一定會呼叫到。也就是說,裝飾器類的重點是其中包含一個原始物件,在呼叫某個方法時,裝飾器類不僅僅會呼叫原始物件的這個方法,你還可以自己給它增加一些方法。如本例中,呼叫裝飾器類的printInfo時,先呼叫了weapon.printInfo(),再呼叫了我們希望它增加的職責System.out.println,那麼程式碼執行結果如下:
一把長劍
有50%機率造成敵人中毒
有50%機率造成致命一擊
有50%機率造成敵人眩暈
裝飾模式比繼承模式更加靈活(可以靈活新增職責),然而,我們必須要為每一個被裝飾的方法新建一個類,如果需要裝飾的方法很多,那麼我們會需要建立數量巨大的裝飾器類,這個時候就要考慮是否要用裝飾模式了。在Java.IO,C#的IO(如OutputStream)中就用到了裝飾器這種模式。需要注意到是,裝飾器類必須要與被裝飾的類繼承於同一介面,這就如最開始所說的,不管長劍鑲嵌了什麼,它總是一把武器。