策略模式和java語言的多態特性有些像。java的多態特性允許我們面向接口編程,不用關心接口的具體實現。接口所指向的實現類,以及通過接口調用的方法的具體行為可以到運行時才綁定。這么做最大的好處是在盡可能實現代碼復用的前提下更好地應對具體實現類的變化。比如我想增加一種接口的實現或者修改原有實現類的某個行為,那我幾乎不用修改任何客戶端代碼。策略模式可以說正是這種思想在設計模式上的運用。它可以使我們更好的復用代碼,同時使程序結構設計更有彈性,更好的應對變化。
2. 策略模式詳解
2.1 策略模式定義
策略模式定義了一系列算法,並將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法獨立於使用它的客戶端而獨立的變化。
可以使用多態進行類比來理解策略模式的定義。一系列算法可以理解成接口的不同實現類,因為不同實現類都實現了相同的接口,因而它們也可以相互替換。策略模式讓算法獨立於客戶端而變化與接口的實現類可以獨立於使用接口的客戶端變化類似。
2.2 策略模式的UML類圖
從UML類圖上可以看出,策略模式中主要有3個角色
-
抽象策略接口
上圖中的Strategy即抽象策略接口,接口中定義了抽象的策略算法algorithm()。 -
具體的策略實現類
上圖中的StrategyA和StrategyB即具體的策略實現。不同的策略實現類都實現了抽象策略接口,並重寫了其抽象策略方法。因為都實現了相同的策略接口,因而算法可以相互替換,並且可以動態的改變具體的算法實現。 -
封裝策略的上下文環境
上圖中的Context即策略的上下文環境。它屏蔽了高層模塊對策略算法的直接訪問,封裝了可能存在的變化。而且提供了修改Strategy的setter方法,可以動態的改變算法的具體實現。
3.策略模式的優點
我們可以結合使用策略模式的例子並與其它實現方案進行對比來看看策略模式到底有什么好處
3.1 一個使用策略模式的例子
定義一個汽車類Car。由於汽車最大的特點是能跑,因而我們賦予該類一個move行為。但要跑起來需要提供能源,通常而言這種能源是汽油,但現在純靠電池驅動的汽車也越來越多。因而Car的move行為就有兩種不同的行為,一種是使用汽油跑,一種是使用電能跑。因而我們可以這么定義
- 抽象的汽車類Car
/**
* @author: takumiCX
* @create: 2018-10-13
**/
public abstract class Car {
//汽車品牌
private String brand;
public Car(String brand) {
this.brand = brand;
}
public Car(String brand, MoveStrategy strategy) {
this.brand = brand;
this.moveStrategy=strategy;
}
//汽車的運行策略:使用汽油運行,使用電能運行等等
private MoveStrategy moveStrategy;
//運行方法
public void move() {
System.out.print(brand);
moveStrategy.move();
}
public void setMoveStrategy(MoveStrategy moveStrategy) {
this.moveStrategy = moveStrategy;
}
}
在抽象汽車類中定義了一個move()方法表示汽車具有運行的行為,但是由於到底是使用汽油運行還是使用電能運行並沒有直接定義在里面,而是調用了策略接口中定義的move方法。該策略接口以組合的方式封裝在Car內部,並提供了setter方法供客戶端動態切換汽車的運行方式。
- 使用汽油運行的策略實現
/**
* @author: takumiCX
* @create: 2018-10-14
**/
/**
* 使用汽油運行的策略實現
*/
public class GasolineMoveStrategy implements MoveStrategy{
@Override
public void move() {
System.out.println(" Use Gasoline Move!");
}
}
- 使用電池運行的策略實現
/**
* @author: takumiCX
* @create: 2018-10-15
**/
/**
* 使用電能運行的策略實現
*/
public class ElectricityMoveStrategy implements MoveStrategy {
@Override
public void move() {
System.out.println(" Use Electricity Move!");
}
}
- 具體的汽車實現類
比如我們通過繼承的方式定義一輛特斯拉汽車,特斯拉汽車默認是純電動的
/**
* @author: takumiCX
* @create: 2018-10-13
**/
public class TeslaCar extends Car {
public TeslaCar(String brand) {
super(brand,new ElectricityMoveStrategy());
}
}
- 客戶端代碼
首先構造一輛特斯拉車觀察其運行方式,並通過setter方法動態改變其運行方式為汽油驅動
/**
* @author: takumiCX
* @create: 2018-10-13
**/
public class Client {
public static void main(String[] args) {
TeslaCar car = new TeslaCar("Tesla");
car.move();
car.setMoveStrategy(new GasolineMoveStrategy());
car.move();
}
}
- 運行結果
3.2 與其他實現方式的對比
其實上面的例子除了使用策略模式外,還有其他實現方式,但它們都有比較明顯的缺點。
3.2.1接口的實現方式
/**
* @author: takumiCX
* @create: 2018-10-15
**/
public interface Move {
void move();
}
並讓抽象父類Car實現它
/**
* @author: takumiCX
* @create: 2018-10-13
**/
public abstract class Car implements Move{
//汽車品牌
private String brand;
public Car(String brand) {
this.brand = brand;
}
}
這樣所有繼承Car的具體汽車類都必須實現自己的move方法,也就是讓具體的汽車子類來決定汽車的具體行為:到底是使用汽油運行還是使用電池運行。但是這么做至少有以下幾個缺點
-
1.具體的汽車運行行為不方便后期維護。因而move行為無法被復用,具體的實現都分散在了子類中。如果要對某種驅動方式的實現進行修改,不得不修改所有子類,這簡直是災難。
-
2.導致類數量的膨脹。同樣品牌的汽車,由於有汽油和電動兩種運行方式,不得不為其維護兩個類,如果在增加一種驅動方式,比如氫能源驅動,那不得為每個品牌的汽車再增加一個類。
-
3.不方便move行為的擴展,也不方便動態的更換其實現方式。
3.2.2 if-else的實現方式
move方法接受客戶端傳遞的參數,通過if-else或者swich-case進行判斷,選擇正確的驅動方式。
public void move(String moveStrategy) {
if("electricity".equals(moveStrategy)){
System.out.println(" Use Electricity Move!");
}else if("gasoline".equals(moveStrategy)){
System.out.println(" Use Gasoline Move!");
}
}
但這樣做相當於硬編碼,不符合開閉原則。比如我要增加一種氫能源的驅動方式,這種實現就需要修改move中的代碼。而如果使用上面說的策略模式,則只需要增加一個實現實現策略接口的具體策略實現類,而不需要修改move中的任何代碼,即可被客戶端所使用。
/**
* @author: takumiCX
* @create: 2018-10-15
**/
public class HydrogenMovetrategy implements MoveStrategy {
@Override
public void move() {
System.out.println(" Use Hydrogen Move!");
}
}
3.3 使用策略模式的優點
-
1.可以優化類結構,當類的某種功能有多種實現時,可以在類中定義策略接口,將真正的功能實現委托給具體的策略實現類。這樣避免了類膨脹,也能更好的進行擴展和維護。
-
2.避免使用多重條件判斷導致的硬編碼和擴展性差的問題
-
3.可以使具體的算法實現自由切換,增強程序設計的彈性。
4. 使用工廠方法模式改進原有策略模式
所有的策略實現都需要對外暴露,上層模塊必須知道具體的策略實現類,這與迪米特法則相違背。為此,可以使用工廠方法模式進行解耦。
- 策略工廠接口
/**
* @author: takumiCX
* @create: 2018-10-16
**/
public interface MoveStrategyFactory {
MoveStrategy create();
}
- 氫能源驅動方式的工廠
/**
* @author: takumiCX
* @create: 2018-10-16
**/
public class HydrogenMoveStrategyFactory implements MoveStrategyFactory {
@Override
public MoveStrategy create() {
return new HydrogenMovetrategy();
}
}
- 客戶端
/**
* @author: takumiCX
* @create: 2018-10-13
**/
public class Client {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
TeslaCar car = new TeslaCar("Tesla");
MoveStrategyFactory factory = new HydrogenMoveStrategyFactory();
MoveStrategy moveStrategy = factory.create();
car.setMoveStrategy(moveStrategy);
car.move();
}
}
這樣我們通過工廠方法模式封裝了具體策略類的創建過程,同時也避免了向高層模塊暴露。最后運行結構如下
5. 總結
當完成某項功能有多種不同的實現時,可以實用策略模式。策略模式封裝了不同的算法,並且使這些算法可以相互替換,這提高了代碼的復用率也增強了程序設計的彈性。並且可以結合其他設計模式比如工廠方法模式向上層模塊屏蔽具體的策略類,使代碼更易於擴展和維護。
5. 參考資料
- 《Head First 設計模式》
- 《設計模式之禪》