設計模式之一——從魔獸爭霸的兵種和技能看策略模式


很多人都喜歡玩魔獸爭霸,里面的兵種很多,比如有Footman步兵、Knight騎士、Grunt獸人步兵,他們使用不同的武器,Footman步兵使用寶劍攻擊、knight也使用sword寶劍攻擊,grunt使用axe斧頭攻擊。我們如何來設計這幾個兵種角色呢?

1 初步設計

首先我們想可以創建一個character角色類,他們都能夠walk走路,但有不同的攻擊行為,可以將character設計成抽象類,利用繼承來得到具體的兵種。設計出來的類應該如圖所示。

 

這種設計通過繼承實現各兵種,footman、knight、grunt可以實現攻擊行為。我們現在想增加兵種kedo科多獸,kedo使用mouth嘴吞噬敵人,還可以用drum戰鼓增加我軍的攻擊力,是個輔助兵種,character中沒有kedo對應的攻擊和輔助方法,kedo不能從character繼承得到。如果想讓kedo繼承character,必須修改character的類結構,增加用嘴攻擊的方法和用鼓輔助的方法。

這里就能看到這個設計存在的問題,第一個是可擴展性不好,不能對適應變化的行為。第二個問題是footman和knight都用寶劍攻擊,swordAttack方法在子類需要重復寫代碼,沒有將代碼復用。

這里我們引入軟件設計的一個原則,就是區分不變的和變化的,將變化的封裝起來。具體到character類來說,就是walk方法是不變的,不用動。attack和assist行為是變化的,我們給封裝起來。

2 第一次改進

將attack和assist行為封裝起來,我們考慮使用接口,設計兩個接口Attackable和Assistable,讓footman、knight、grunt、kedo分別實現相應的接口。設計出來的類如下圖所示。

 

我們看這個設計方法,第一個可擴展的問題,如果還有其他的行為,我們可以再加入接口,這樣可擴展的問題就解決了。第二個問題,代碼重復的問題,由於footman和knight的swordAttack實現Attackable接口的attack方法,還是存在代碼重復的問題。

解決代碼重復的問題,我們考慮將attack和assist兩種行為從character中分離出來。

3 第二次改進

3.1 分離出行為

我們設計兩個行為的接口,AttackBehavior和AssistBehavior,子類來實現對應的attack和assist行為。接口和實現類的設計如圖所示。

  

此處使用了軟件設計的一個原則,針對接口編程,不要針對實現編程。針對接口編程,可以使用面向對象的多態特性,比如一個兵種有AttackBehavior的attack行為,這個attack行為在實現的時候可以是SwordAttack,也可以AxeAttack或者MouthAttack,增加靈活性。

3.2 整合兵種的行為

在Character類中增加兩個實例變量,分別是attackBehavior和assistBehavior,聲明為接口類型。Character設計如圖所示。

 

3.3 代碼實現

以下是Character類的代碼實現

public class Character {
    AttackBehavior attackBehavior;
    AssistBehavior assistBehavior;
   
    public void walk(){
        System.out.println("I can walk!");
    }
   
    public void performAttack(){
        attackBehavior.attack();
    }
   
    public void performAssist(){
        assistBehavior.assist();
    }
}

以下是Kedo科多獸的主要代碼

public class Kedo extends Character {
    public Kedo(){
        attackBehavior = new MouthAttack();
        assistBehavior = new DrumAssist();
    }
}

我們在這里將兵種的攻擊或者輔助行為單獨設計為類,與兵種分離開來,這種做法讓兵種和行為都是獨立的個體,他們的結合方式變成了組合,而不是一開始的繼承。這里用到了軟件設計中一個重要的原則,多用組合,少用繼承。使用組合建立的系統具有很大的彈性。

我們的代碼Kedo科多獸類,有用嘴吞噬和用戰鼓輔助的行為,代碼把他的行為固定了,如果可以不把他的行為固定,動態的設定他的行為,則可以增加代碼的靈活性。我們可以進一步改進代碼。

3.4動態設定行為

在Character類中,增加兩個方法:

public void setAttackBehavior(AttackBehavior atb){
    attackBehavior = atb;
}

public void setAssistBehavior(AssistBehavior asb){
    assistBehavior = asb;
}

這樣我們如果實現新的兵種,比如 Raider狼騎士 ,他是用大刀(暫時用寶劍代替)砍,我們可以不創建raider這個兵種類,直接實現raider具有寶劍攻擊的行為。代碼如下。

Character raider = new Character();
raider.setAttackBehavior(new SwordAttack());
raider.performAttack();

4 策略模式

現在我們回過頭來再看看整個思路,兵種具有不同的攻擊或輔助行為,我們把行為分離出來進行定義,這樣就定義了一組行為(SwordAttack、AxeAttack、MouthAttack、DrumAssist),並把這些行為進行了封裝,都封裝到了類中,這些行為在地位上平等的,可以互相替代,行為獨立於兵種之外,這就是策略模式的設計思路。

我們把行為擴展到更廣義的算法,一組行為我們擴展為算法簇。再把上面的設計思路說一次,就是策略模式,定義了算法簇,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立於使用算法的客戶。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM