設計模式中的多型——策略模式詳解
1. 關於策略模式
策略模式和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. 總結
當完成某項功能有多種不同的實現時,可以實用策略模式。策略模式封裝了不同的演算法,並且使這些演算法可以相互替換,這提高了程式碼的複用率也增強了程式設計的彈性。並且可以結合其他設計模式比如工廠方法模式向上層模組遮蔽具體的策略類,使程式碼更易於擴充套件和維護。