策略(Strategy)模式(一)
參考資料:
概念解析
策略模式strategy,物件行為型
實現一個功能有多種演算法或者策略,可以依據環境或者條件的不同選擇不同的演算法或者策略來完成此功能。 比如查詢、排序等。 常用的編碼方式:
1. 建立一個類,在類中提供多個方法,每個方法對應一個具體的查詢方法。一個簡單的例子,DateUtils工具類,獲取時間的方式有很多種,對外提供很多獲取時間的方法。
2. 或者是將查詢,排序的演算法封裝在一個統一的方法中,通過if else的方法進行選擇,比如if(冒泡){冒泡的方法}else if(倒序){倒序的方法}
以上這2中都是硬編碼,如果需要新增一個新的查詢或者排序演算法,那麼就要修改封裝的演算法類的原始碼DateUtils;如果是更換了演算法,比如DateUtils.getYear()更改名稱成DateUtils.loadYear(),那麼客戶端也要修改呼叫的程式碼。這樣在這個類中就會封裝大量的查詢演算法,越來越複雜,維護起來也會變得很困難。
headfirst書上的例子拿來用一下: 一隻鴨子,它的行為模式:可以飛fly()、叫duack()、走walk()、游泳swim()。 但是鴨子是有很多種的:真的鴨子,洗澡的玩具鴨,小黃鴨,牆上的塗鴉等,這些鴨子有的可以飛,有的不能遇水,有的不能叫。 而且就duack而言,也有很多種叫法,有的嘎嘎嘎,有的呀呀呀。。。
那麼如何設計這個鴨子物件,讓其產生的子鴨子可以飛,可以叫,而且發出不同的叫聲呢?這裡就可以用到策略模式來設計。
整體思路就是抽取鴨子的行為(演算法),也就是演算法(具體行為)和物件(鴨)分離。策略模式把物件本身和運算規則區分開來,定義一系列的演算法,把每個演算法封裝起來,並且使其可以相互替換。也就是一個鴨子可以設定為嘎嘎叫,也可以替換為呀呀叫。
應用場景
直接上應用的場景吧,大部分人應該關注的是這點,場景也不是絕對的。此部分參考百度百科
1. 多個類只區別在表現行為不同,可以使用Strategy模式,在執行時動態選擇具體要執行的行為(鴨叫分為很多種)。
2. 需要在不同的情況下使用不同的策略(演算法),或者策略還可能在未來用其他方式來實現(克隆出現了一隻咩咩叫的鴨子)。
3. 對客戶隱藏具體的策略(演算法)的實現細節,彼此完全獨立(可以不需要知道我怎麼實現的叫聲,只需要知道我提供了多少種叫法,呼叫就好了)。
模式的組成
例子 上圖中定義了一個person,它要去旅遊(旅遊是一個行為動作),選擇出行方式可以有很多種,飛機,火車,自行車。但是我們去實現這個person的時候並不要在這個類中實現所有的出行方式,而是通過一個介面方法。person中維護一個介面的呼叫。至於具體的出行(strategy.travel())的具體實現,就是具體的行為實現。 重點注意UML中的setStrategy(TravelStrategy strategy)
標準點的圖:
—抽象策略角色(Strategy): 策略類,通常由一個介面或者抽象類實現。定義所有支援的演算法的公共介面。 Context使用這個介面來呼叫某ConcreteStrategy定義的演算法。 —具體策略角色(ContentStrategy):以Strategy介面實現某具體演算法。 —環境角色(Content):持有一個策略類的引用,最終給客戶端呼叫。用一個ConcreteStrategy物件來配置。維護一個對Strategy物件的引用。可定義一個介面來讓Strategy訪問它的資料。
程式碼實現
先來個題目做一下,來自headfirst的拷問: 一句話概括:不同的遊戲角色可以使用不同的武器進行戰鬥,如何實現?
程式碼部分 首先觀測到行為,使用武器戰鬥,那麼就要抽象出一個使用武器的介面
public interface WeaponBehavior {
/**
* 攻擊
*/
void useWeapon();
}
使用什麼武器呢(具體行為實現)?假設為使用寶劍戰鬥
public class SwordBehavior implements WeaponBehavior {
@Override
public void useWeapon() {
System.out.println("使用寶劍攻擊.");
}
}
好了,行為有了,該設定角色Character了,這個角色有使用武器的行為,並且有戰鬥方法:
public abstract class Character {
public Character() {
}
/**
* 使用武器
*/
WeaponBehavior weapon;
public void setWeapon(WeaponBehavior weapon) {
this.weapon = weapon;
}
/**
* 戰鬥
*/
public void fight() {
weapon.useWeapon();
}
}
角色有了,該建立具體的人物了,來個king試試,他喜歡用寶劍。
public class King extends Character {
public King(){
weapon = new SwordBehavior();
}
}
注意這裡的weapon,是繼承自Character的,而且在king建立的時候,也建立了介面的實現類為SwordBehavior,那麼他就有了具體的行為實現。
再來一個queen,她喜歡用弓箭。
public class Queen extends Character {
public Queen(){
weapon = new BowAndArrowBehavior();
}
}
是時候開始戰鬥了。
@Test
public void testFight(){
// 建立一個king
Character k = new King();
Character q = new Queen();
k.fight();
System.out.println("queen血量 -1");
q.fight();
System.out.println("king血量 -1");
System.out.println("king的寶劍掉了...");
System.out.println("king撿起了弓箭...");
k.setWeapon(new BowAndArrowBehavior());
k.fight();
System.out.println("queen血量 -1");
System.out.println("king獲得了勝利");
}
使用寶劍攻擊.
queen血量 -1
使用弓箭攻擊.
king血量 -1
king的寶劍掉了...
king撿起了弓箭...
使用弓箭攻擊.
queen血量 -1
king獲得了勝利
看到效果了,king的寶劍被queen打掉了,但是king迅速的撿起了地上的弓箭,反敗為勝。
小夥伴可以去試著實現鴨子發出不同叫聲
優缺點
優點:
1. 策略模式提供了管理相關的演算法族的辦法(演算法族就是在strategy介面中定義的一系列方法,程式碼舉例中只有useWeapon(),但是你還可以加入其它的方法,比如useShield(),或者使用魔法等等)。Strategy類層次為Context定義了一系列的可供重用的演算法或行為。繼承有助於析取出這些演算法中的公共功能。
2. 策略模式提供了可以替換繼承關係的辦法。
3. 消除了一些if else條件語句 :Strategy模式提供了用條件語句選擇所需的行為以外的另一種選擇。當不同的行為堆砌在一個類中時 ,很難避免使用條件語句來選擇合適的行為。將行為封裝在一個個獨立的Strategy類中消除了這些條件語句。含有許多條件語句的程式碼通常意味著需要使用Strategy模式。
4. 實現的選擇 Strategy模式可以提供相同行為的不同實現。客戶可以根據不同時間 /空間權衡取捨要求從不同策略中進行選擇。
缺點
1. 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味著客戶端必須理解這些演算法的區別,以便適時選擇恰當的演算法類。換言之,策略模式只適用於客戶端知道所有的演算法或行為的情況。
2. 策略模式造成很多的策略類,每個具體策略類都會產生一個新類(實現了介面的類)。有時候可以通過把依賴於環境的狀態儲存到客戶端裡面,而將策略類設計成可共享的,這樣策略類例項可以被不同客戶端使用。換言之,可以使用享元模式來減少物件的數量。
總結
摘抄自blog1
1)策略模式是一個比較容易理解和使用的設計模式,策略模式是對演算法的封裝,它把演算法的責任和演算法本身分割開,委派給不同的物件管理。策略模式通常把一個系列的演算法封裝到一系列的策略類裡面,作為一個抽象策略類的子類。用一句話來說,就是“準備一組演算法,並將每一個演算法封裝起來,使得它們可以互換”。
2)在策略模式中,應當由客戶端自己決定在什麼情況下使用什麼具體策略角色。
3)策略模式僅僅封裝演算法,提供新演算法插入到已有系統中,以及老演算法從系統中“退休”的方便,策略模式並不決定在何時使用何種演算法,演算法的選擇由客戶端來決定。這在一定程度上提高了系統的靈活性,但是客戶端需要理解所有具體策略類之間的區別,以便選擇合適的演算法,這也是策略模式的缺點之一,在一定程度上增加了客戶端的使用難度。
headfirst讀書分享
1. 找出應用中可能需要變化之處,將他們獨立出來,不要和不需要變化的程式碼混在一起。
2. 針對介面程式設計,而不是針對實現程式設計。
3. 多用組合,少用繼承。
設計模式讓你和其他開發人員之間有共享的詞彙,一旦懂得這些詞彙,和其他開發人員之間的溝通就會很容易。
設計模式也可以幫助你提升思考架構的層次號模式層面,而不是停留在瑣碎的物件上。
設計模式不會直接進入你的程式碼中,而是必須先進入你的『腦袋』中。一旦你先在腦海中裝入許多 模式的知識,就能夠開始在新設計中採用它們,以及當你的舊程式碼變得如同義大利麵一樣攪和成一 團沒有彈性時,可用它們重做舊程式碼。
連線:https://blog.csdn.net/hguisu/article/details/7558249 ↩︎