1. 程式人生 > >Java設計模式之再從[暗黑破壞神"裝備鑲嵌寶石系統"]分析裝飾(Decorator)模式

Java設計模式之再從[暗黑破壞神"裝備鑲嵌寶石系統"]分析裝飾(Decorator)模式

  在暗黑破壞神等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)中就用到了裝飾器這種模式。需要注意到是,裝飾器類必須要與被裝飾的類繼承於同一介面,這就如最開始所說的,不管長劍鑲嵌了什麼,它總是一把武器。