Java設計模式----------策略模式
以下內容來自HeadFIrst設計模式一書和http://www.cnblogs.com/xrq730/p/4906313.html的博文,作為自己的學習筆記總結如下。
HeadFirst設計模式一書在開篇就提出了三條設計的原則:
1.找出應用中可能需要變化之處,將它們獨立出來,不要和那些需要變化的代碼混在一起。
2.針對接口編程,而不是針對實現編程。
3.多用組合,少用繼承。
策略模式------定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立於使用算法的客戶。
以書中的鴨子遊戲SimUDuck為例進行分析:
鴨子遊戲SimUDuck中有各種鴨子,有遊泳和呱呱叫的功能,最初的超類如下圖所示:
後來想要為部分鴨子添加會飛的功能,程序員Joe直接在父類上添加了fly(),導致超級父類變成下圖:
這就導致所有的鴨子都具備的飛的功能,這顯然是不符合常理的。並且同理,並不是所有的鴨子都有Quack的功能,比如橡皮鴨。於是程序員Joey將quack()和fly()兩個方法都抽離出來,實現成兩個接口Quackable和Flyable,只要是會飛的鴨子就實現Quackable接口即可,會飛實現Flyable接口即可。
但是這導致了代碼無法復用的問題,每個鴨子子類,只要是會飛就必須自己實現Flyable接口中的fly()一次,如果有100個會飛的鴨子子類,這個fly方法就必須同樣的書寫100次,這顯然極不合理。
這時,應用以上三條設計原則:
1.將變化的和不變的進行分離------Duck類仍然是所有鴨子的超類,但是每個鴨子子類的飛行和呱呱叫的行為是不同的,需要將這兩個行為取出,抽象成行為接口FlyBehavior和QuackBehavior;
2.針對接口編程,而不是針對實現進行編程----------將鴨子的行為放在專門的行為類中,這些行為類需要實現相應的行為接口,而不是由Duck類實現行為接口。這樣的設計可以使這些行為類被其他對象復用,比如天鵝類也可以飛,大雁類也可以飛,青蛙也可以呱呱叫,這使得這些行為與鴨子類無關。
3. 多用組合,少用繼承-------------每一個鴨子子類都有一個FlyBehavior和QuackBehavior 接口的實例化對象,讓鴨子將行為由這兩個對象進行處理。 不同於以往的做法,我們可以看到鴨子的行為不是繼承而來,而是和行為接口的實例化對象組合而來。具備這個行為的鴨子對象和行為的實例化對象是has-a的關系。
實現如下:
父類:
package Test; public abstract class Duck { QuackBehavior quackBehavior;//註意,持有的對象類型是接口類型 FlyBehavior flyBehavior; public void swim(){ System.out.println("I can swim"); } public abstract void display(); public void performQuack(){//調用相應的行為方法 quackBehavior.quack(); } public void perfromFly(){ flyBehavior.fly(); } public void setQuackBehavior(QuackBehavior quackBehavior){//可以動態的對行為進行設置 this.quackBehavior=quackBehavior; } public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } }
飛行接口
package Test; public interface FlyBehavior { public void fly(); }
嘎嘎叫接口
package Test; public interface QuackBehavior { public void quack(); }
具體行為實現:
package Test; public class FlyWithWings implements FlyBehavior { @Override public void fly() { System.out.println("我有一雙隱形的翅膀帶我飛翔"); } }
package Test; public class FlyNoWay implements FlyBehavior { @Override public void fly() { System.out.println("I can‘t fly"); } }
package Test; public class Quack implements QuackBehavior { @Override public void quack() { System.out.println("嘎嘎嘎"); } }
測試類:
package Test; public class MiniDuckSimulator { public static void main(String[] args){ Duck mallard=new MallardDuck(); mallard.display(); mallard.performQuack(); mallard.perfromFly(); mallard.setFlyBehavior(new FlyNoWay()); mallard.perfromFly(); } }
輸出:
策略模式的總結
以下內容轉自:http://www.cnblogs.com/xrq730/p/4906313.html
策略模式
策略模式的用意是針對一組算法,將每一個算法封裝到具有共同接口的獨立類中,從而使得它們可以相互替換。策略模式使得算法可以在不影響到客戶端的情況下發生變化。
策略模式的結構
策略模式是對算法的包裝,是把使用算法的責任和算法本身分開。策略模式通常是把一系列的算法包裝到一系列的策略類裏面,作為一個抽象策略類的子類。
策略模式涉及到三個角色:
1、環境角色
持有一個策略Strategy的引用
2、抽象策略角色
這是一個抽象角色,通常由一個接口或抽象類實現,此角色給出所有具體策略類所需的接口
3、具體策略角色
包裝了相關算法或行為
策略模式示例
有一個抽象的策略接口:
public interface Strategy { public void useStrategy(); }
實現兩種具體的策略:
public class StrategyA implements Strategy { public void useStrategy() { System.out.println("StrategyA.useStrategy()"); } }
public class StrategyB implements Strategy { public void useStrategy() { System.out.println("StrategyB.useStrategy()"); } }
某個類持有策略的引用:
public class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void strategyMethod() { strategy.useStrategy(); } }
調用這個類的地方可以自行決定使用哪種策略:
public class TestMain { public static void main(String[] args) { Strategy strategyA = new StrategyA(); Strategy strategyB = new StrategyB(); Context context = new Context(strategyA); context.strategyMethod(); context = new Context(strategyB); context.strategyMethod(); } }
策略模式的使用場景
1、購物系統
舉一個實際例子吧。假如有一個購物系統,在用戶付款的時候,會產生很多場景,根據用戶的不同情況算出不同用戶要付款的金額,這時候最直觀的一種做法是:
在付款的裏面寫N多的if...else if...else,判斷用戶的場景,根據場景計算用戶付款金額。
這種設計明顯違反了開閉原則。開閉原則的"閉",指的是對修改關閉,但是這裏假如算法又多了幾種,那麽必須再次修改這個付款的類。
這時候就可以使用策略模式。在付款的類裏面持有一個付款接口的引用,每次根據不同場景傳入一個具體的策略就好了。比如A類中要使用S0算法,就傳入一個S0策略;B類中要使用S1算法,就傳入一個S1算法。不需要把判斷都放在付款的類中,代碼的可讀性、可維護性也更高了。付款這個類甚至可以直接生成一個.class文件放在一個jar包裏面供調用。
2、使得代碼更優雅、更易維護
假如你的代碼中某處有一個打分系統,你為這個打分系統寫了一段非常長的邏輯,某天,產品部門的同事找你,給我換一段打分邏輯,此時,有兩種做法:
(1)把原有的打分邏輯刪除,但這麽做一個缺點是是看不到以前的打分算法了,另一個缺點是如果以後打分算法要換回來就找不到代碼,雖然SVN和GIT這種版本管理工具都有歷史提交記錄的功能,但還是顯得麻煩
(2)把原有的打分邏輯註釋,但這麽做的最大缺點是代碼中有大量的註釋,尤其在策略邏輯非常長的時候,這就導致了代碼的可讀性非常差
此時,就可以使用策略模式,將打分邏輯抽象為一種策略,換打分策略,新增一個策略的實現類,最後再讓代碼中傳入新的策略實現類即可。
策略模式在Java中的應用及解讀
策略模式在Java中的應用,這個太明顯了,因為Comparator這個接口簡直就是為策略模式而生的。Comparable和Comparator的區別一文中,詳細講了Comparator的使用。比方說Collections裏面有一個sort方法,因為集合裏面的元素有可能是復合對象,復合對象並不像基本數據類型,可以根據大小排序,復合對象怎麽排序呢?基於這個問題考慮,Java要求如果定義的復合對象要有排序的功能,就自行實現Comparable接口或Comparator接口,看一下sort帶Comparator的重載方法:
1 public static <T> void sort(List<T> list, Comparator<? super T> c) { 2 Object[] a = list.toArray(); 3 Arrays.sort(a, (Comparator)c); 4 ListIterator i = list.listIterator(); 5 for (int j=0; j<a.length; j++) { 6 i.next(); 7 i.set(a[j]); 8 } 9 }
跟一下第3行:
1 public static <T> void sort(T[] a, Comparator<? super T> c) { 2 T[] aux = (T[])a.clone(); 3 if (c==null) 4 mergeSort(aux, a, 0, a.length, 0); 5 else 6 mergeSort(aux, a, 0, a.length, 0, c); 7 }
傳入的c不為null,跟一下第6行的mergeSort:
1 private static void mergeSort(Object[] src, 2 Object[] dest, 3 int low, int high, int off, 4 Comparator c) { 5 int length = high - low; 6 7 // Insertion sort on smallest arrays 8 if (length < INSERTIONSORT_THRESHOLD) { 9 for (int i=low; i<high; i++) 10 for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--) 11 swap(dest, j, j-1); 12 return; 13 } 14 15 // Recursively sort halves of dest into src 16 int destLow = low; 17 int destHigh = high; 18 low += off; 19 high += off; 20 int mid = (low + high) >>> 1; 21 mergeSort(dest, src, low, mid, -off, c); 22 mergeSort(dest, src, mid, high, -off, c); 23 24 // If list is already sorted, just copy from src to dest. This is an 25 // optimization that results in faster sorts for nearly ordered lists. 26 if (c.compare(src[mid-1], src[mid]) <= 0) { 27 System.arraycopy(src, low, dest, destLow, length); 28 return; 29 } 30 31 // Merge sorted halves (now in src) into dest 32 for(int i = destLow, p = low, q = mid; i < destHigh; i++) { 33 if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0) 34 dest[i] = src[p++]; 35 else 36 dest[i] = src[q++]; 37 } 38 }
第10行,根據Comparator接口實現類的compare方法的返回結果決定是否要swap(交換)。
這就是策略模式,我們可以給Collections的sort方法傳入不同的Comparator的實現類作為不同的比較策略。不同的比較策略,對同一個集合,可能會產生不同的排序結果。
認識策略模式
應當明白,策略模式的重心不是如何實現算法(就如同工廠模式的重心不是工廠中如何產生具體子類一樣),而是如何組織、調用這些算法,從而讓程序結構更靈活,具有更好的維護性和擴展性。 軟件開發在完成後的維護時間要比開發時間多的多,因為可能客戶的需求會有變化,所以我們應該致力於提高可維護性和可擴展性上。策略模式降低了程序之間的耦合度,使得程序的維護更加容易。
策略模式有一個很大的特點就是各策略算法的平等性。對於一系列具體的策略算法,大家的地位是完全一樣的,正因為這個平等性,各個算法之間才可以相互替換。
運行期間,每一個時刻只能使用一個具體的策略實現對象,雖然可以動態地在不同的策略中實現切換。
策略模式的優缺點
優點
1、避免了多重條件if...else if...else語句,多重條件語句並不容易維護
2、策略模式提供了管理相關算法簇的辦法,恰當使用繼承可以把公共代碼移到父類,從而避免了代碼重復
缺點
1、客戶端必須知道所有的策略類,並自行決定使用 哪一個策略,這意味著客戶端必須理解這些算法的區別,以便選擇恰當的算法
2、如果備選策略很多,對象的數據會很多
另一個比較好的實例是:轉自https://www.cnblogs.com/wuyudong/p/5924223.html
舉個例子,計算公交車和地鐵運行指定路程後所需的票價
package com.wuyudong.strategy.normal; public class PriceCalculator { // 公交車類型 private static final int BUS = 1; // 地鐵類型 private static final int SUBWAY = 2; public static void main(String[] args) { PriceCalculator calculator = new PriceCalculator(); System.out.println("坐10公裏的公交車的票價為:" + calculator.calculatePrice(10, BUS)); System.out.println("坐10公裏的地鐵的票價為:" + calculator.calculatePrice(10, SUBWAY)); } //計算公交價格 private int busPrice(int km) { int extraTotal = km - 10; int extraFactor = extraTotal / 5; int fraction = extraTotal % 5; int price = 1 + extraFactor * 1; return fraction > 0 ? ++price : price; } //計算地鐵價格 private int subwayPrice(int km) { if (km <= 6) { return 3; } else if (km > 6 && km < 12) { return 4; } else if (km < 22 && km > 12) { return 5; } else if (km < 32 && km > 22) { return 6; } return 7; } //根據類型來計算相應的價格 private int calculatePrice(int km, int type) { if (type == BUS) { return busPrice(km); } else { return subwayPrice(km); } } }
如果再添加一種出租車的價格計算,添加相應的代碼:
public class PriceCalculator { // 公交車類型 private static final int BUS = 1; // 地鐵類型 private static final int SUBWAY = 2; // 出租車類型 private static final int TAXI = 3; public static void main(String[] args) { PriceCalculator calculator = new PriceCalculator(); System.out.println("坐10公裏的公交車的票價為:" + calculator.calculatePrice(10, BUS)); System.out.println("坐10公裏的地鐵的票價為:" + calculator.calculatePrice(10, SUBWAY)); } // 計算出租車價格 private int taxiprice(int km) { return km * 2; } // 根據類型來計算相應的價格 private int calculatePrice(int km, int type) { if (type == BUS) { return busPrice(km); } else if (type == SUBWAY) { return subwayPrice(km); } else { return taxiprice(km); } } }
可見上面的代碼耦合性較高,每當增加新的交通工具類型的時候,需要不斷的修改大量的代碼,這裏使用策略模式重構:
首先定義一個抽象的價格計算接口:
//計算接口 public interface CalculateStrategy { int calculatePrice(int km); }
每一種出行方式都定義一個獨立的計算策略類:
公交車計算策略
public class BusStrategy implements CalculateStrategy { public int calculatePrice(int km) { int extraTotal = km - 10; int extraFactor = extraTotal / 5; int fraction = extraTotal % 5; int price = 1 + extraFactor * 1; return fraction > 0 ? ++price : price; } }
地鐵計算策略
public class SubwayStrategy implements CalculateStrategy { public int calculatePrice(int km) { if (km <= 6) { return 3; } else if (km > 6 && km < 12) { return 4; } else if (km < 22 && km > 12) { return 5; } else if (km < 32 && km > 22) { return 6; } return 7; } }
再創建一個扮演Context的角色,代碼如下:
public class TranficCalculator { CalculateStrategy mStrategy; public static void main(String[] args) { TranficCalculator calculator = new TranficCalculator(); //設置計算策略 calculator.setStrategy(new BusStrategy()); //計算價格 System.out.println("公交車乘10公裏的價格:" + calculator.calculatePrice(10)); } public void setStrategy(CalculateStrategy mStrategy) { this.mStrategy = mStrategy; } public int calculatePrice(int km) { return mStrategy.calculatePrice(km); } }
這樣即使需要添加出租車的價格計算,只需要簡單的新建一個類,讓其繼承自CalculateStrategy接口並實現其中的方法即可。
Java設計模式----------策略模式