設計模式(一) - 策略模式(Strategy pattern)

策略模式是什麼?

策略模式定義了算法族,分別封裝起來,讓它們之間可以互相替換。此模式讓算法的變化獨立於使用算法的客戶。

爲什麼使用策略模式?

優點:

  • 策略模式提供了對“開閉原則”的完美支持,用戶可以在不修改原有系統的基礎上選擇算法或行爲,也可以靈活地-增加新的算法或行爲。
  • 策略模式提供了管理相關的算法族的辦法。
  • 策略模式提供了可以替換繼承關係的辦法。
  • 使用策略模式可以避免使用多重條件轉移語句。

缺點:
  • 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。
  • 策略模式將造成產生很多策略類,可以通過使用享元模式在一定程度上減少對象的數量。

什麼時候使用策略模式?
  • 如果在一個系統裏面有許多類,它們之間的區別僅在於它們的行爲,那麼使用策略模式可以動態地讓一個對象在
許多行爲中選擇一種行爲。
  • 一個系統需要動態地在幾種算法中選擇一種。
  • 如果一個對象有很多的行爲,如果不用恰當的模式,這些行爲就只好使用多重的條件選擇語句來實現。
  • 不希望客戶端知道複雜的、與算法相關的數據結構,在具體策略類中封裝算法和相關的數據結構,提高算法的
保密性與安全性。

使用示例:
該示例取自一個動作冒險遊戲,我們將看到代表遊戲角色的類和角色可以使用的武器行爲的類。每個角色一次只能
使用一種武器。但是可以在遊戲的過程中切換武器。
1.畫UML圖:

2.UML圖分析:
Character(角色)是抽象類,由具體的角色來繼承。而Weapon(武器)是接口,由具體的武器來繼承。所有實際
的角色和武器都是具體類。
任何角色如果想換武器,可以調用setWeapon()方法,此方法定義在Character超類中。可用getWeapon()方法來
獲取所使用的武器,來攻擊其他角色。
3.代碼示例:
Base包:
/**
 * 角色基類
 */
public abstract class Character {
    private WeaponBehavior weapon;

    public abstract void fight();

    public void setWeapon(WeaponBehavior weapon) {
        this.weapon = weapon;
    }

    public void getWeapon() {
        weapon.useWeapon();
    }
}

/**
 * 武器接口
 */
public interface WeaponBehavior {
    void useWeapon();
}

derive包:
角色role包:
/**
 * 國王
 */
public class King extends Character {
    @Override
    public void fight() {
        System.out.println("I'm the King.My attract force is 95.");
    }
}
/**
 * 皇后
 */
public class Queen extends Character {
    @Override
    public void fight() {
        System.out.println("I'm the Queen.My attract force is 90.");
    }
}
/**
 * 騎士
 */
public class Knight extends Character {
    @Override
    public void fight() {
        System.out.println("I'm the Knight.My attract force is 92.5.");
    }
}
/**
 * 妖怪
 */
public class Troll extends Character {
    @Override
    public void fight() {
        System.out.println("I'm the troll.My attract force is 100.");
    }
}
behavior包:
/**
 * 實現用寶劍揮舞
 */
public class SwordBehavior implements WeaponBehavior{
    @Override
    public void useWeapon() {
        System.out.println("I use the sword to attract.");
    }
}
/**
 * 實現用匕首刺殺
 */
public class KnifeBehavior implements WeaponBehavior {
    @Override
    public void useWeapon() {
        System.out.println("I use the knife to attract.");
    }
}
/**
 * 實現用弓箭射擊
 */
public class BowAndArrowBehavior implements WeaponBehavior {
    @Override
    public void useWeapon() {
        System.out.println("I use the bow and arrow to attract.");
    }
}
/**
 * 實現用斧頭劈砍
 */
public class AxeBehavior implements WeaponBehavior {
    @Override
    public void useWeapon() {
        System.out.println("I use the axe to attract.");
    }
}
測試類:
public class TestTroll {
    public static void main(String[] args) {
        Character character = new Troll();
        character.setWeapon(new AxeBehavior());
        character.fight();
        character.getWeapon();
    }
}
測試結果:
I'm the troll.My attract force is 100.
I use the axe to attract.
總結:
1.我們在實例化Troll(妖怪)對象的時候,沒有采用Troll troll = new Troll();這樣的方式,這是因爲我們用到了一個
設計原則針對接口編程,而不是針對實現編程。
“針對接口編程”的關鍵在於多態。利用多態,程序可以針對超類型編程,執行時會根據實際狀況執行到真正的行爲,
不會被綁死再超類型的行爲上。“針對超類性編程”這句話,可以更明確地說成“變量的聲明類型應該是超類型,
通常是一個抽象類或者是一個接口,如此,只要是具體實現此超類型的類所產生的對象,都可以指定給這個變量。
這也意味着,聲明類時不用理會以後執行時的真正對象類型!”
例如:
Dog d = new Dog();
d.bark();
就是明顯的“針對實現編程”,聲明變量“d”爲Dog類型(是Animal的具體實現),會造成我們必須針對具體實現編碼。
但是“針對接口/超類型編程”做法會如下:
Animal animal = new Dog();
animal.makeSound();
我們知道該對象是狗,但是我們現在利用animal進行多態的調用。
更棒的是,子類實例化的動作不再需要在代碼中硬編碼,例如 new Dog(),而是“在運行時才指定具體實現的對象”。
animal = getAnimal();
animal.makeSound();
我們不知道實際的子類型是“什麼”... 我們只關心它知道如何正確地進行makeSound()的動作就夠了。
Character基類中的武器類型聲明爲接口類型WeaponBehavior也是基於這個設計原則設計的。
2.第二個設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼
混在一起。
在這個小程序demo中,因爲每個角色都會“戰鬥”,所以fight相關的是寫在基類中待繼承的,它是不會變化的,
每個角色所使用的“武器”都是不同的,故武器是會變化的,所以爲了分開“變化和不會變化的部分”,我們把“武器”
從Character類中取出來,建立一組新類來代表每個行爲。
而我們以WeaponBehavior爲接口,然後使用SwordBehavior等實現類來實現這個父接口,這樣的設計,可以讓
“武器”的動作被其他的對象複用,因爲這些行爲已經與角色類無關了。這樣一來,有了繼承的“複用”的好處,卻沒
有繼承所帶來的包袱(比如高耦合),而我們可以新增一些行爲,不會影響到既有的行爲類,也不會影響“使用”到
武器行爲的角色類。
3.第三個設計原則:多用組合,少用繼承。
我們描述事情的方式也稍有改變。不再把角色的行爲說成是“一組行爲”,我們開始把行爲想成是“一族算法”。
想象下,我們後期有一個需求變更,需要給每個角色增加一個“寵物”,可以用來幫助戰鬥,每個人的寵物自然也會
可能是不同的,我們可以說每個角色“有一個”武器和寵物。“有一個”關係相當有趣:每一個角色都有一個
WeaponBehavior和PetBehavior,好將武器和寵物委託給它們代爲處理。
當我們將兩個類結合起來使用,如同本例一樣,這就是組合(composition)。
這種做法和“繼承”不同的地方在於,角色的行爲不是繼承來的,而是和適當的行爲對象“組合”來的。這使我們所
建立的系統具有很大的彈性,不僅可將算法族封裝成類,還可以“在運行時動態地改變行爲”,只要組合的行爲對象
符合正確的接口標準即可。

良好的OO設計必須具備可複用、可擴充、可維護三個特性。

ps:第一篇博客介紹策略模式顯的有點囉嗦和長篇大論了點,但細讀下來相信你會有所收穫的~感謝閱讀。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章