掃盲:策略模式,成事兒還需要策略
什麼是策略模式?
生活中的策略
策略模式在生活中體現很多。
我們要去旅遊,我們可以選擇不同的出行方式:飛機,火車,大巴,自駕等,這是不同的策略。
雙十一當當網購買滿減活動,滿 100 減 50,滿 200 減 100,滿 400 減 250 等,這也是不同的策略。
抑或是我們在追求女生時,針對不同性格的女孩子採用不同的方式,這還是不同的策略。
程式中的策略
策略模式在程式中的體現依然淋漓盡致。
比如我們的圖片載入,Android 上有 Fresco
,Picasso
,Glide
,Universal-Image-Loader
等,iOS 上有 SDWebImage
、AFNetworking
、FastImageCache
所以,假設讓你來設計一個圖片載入上層框架,要求可以底層可以使用 A B 兩種載入策略,你會怎麼做呢?
// 載入類A public class ImageLoadServiceA { public void loadImage() { System.out.println("使用 A 載入框架"); } } // 載入類B public class ImageLoadServiceB { public void loadImage() { System.out.println("使用 B 載入框架"); } } // 使用 public void loadNetImage(boolean useA) { if(useA){ new ImageLoadServiceA().loadImage();// 使用A載入方式 } else { new ImageLoadServiceB().loadImage();// 使用B載入方式 } }
可以看到,上述通過一個 useA
引數判斷是否使用 A 框架,為 true
使用 A,否則使用 B 框架進行載入。
使用簡單工廠模式應對
但假設我們現在需要再支援一個 C 框架的使用,你可能想到了,那就再加一個 boolean 引數 useB
即可,或者直接使用一個 int 引數 loadType
,巨集定義 0 代表 A 框架,1 代表 B 框架,2 代表 C 框架,這樣如果需要增加方式則更新取值即可。
設計模式不過是我們寫程式的招式,由於之前大家可能還學習過了簡單工廠模式,我們不妨在這裡進行實戰。
// 抽象圖片載入類 public abstract class ImageLoadService { public abstract void loadImage(); } // 具體載入類A public class ImageLoadServiceA extends ImageLoadService { @Override public void loadImage() { System.out.println("使用 A 載入框架"); } } //具體載入類B public class ImageLoadServiceB extends ImageLoadService { @Override public void loadImage() { System.out.println("使用 B 載入框架"); } } //具體載入類C public class ImageLoadServiceC extends ImageLoadService { @Override public void loadImage() { System.out.println("使用 C 載入框架"); } } public class ImageLoadFactory { public static ImageLoadService create(int loadType) { ImageLoadService loadService = null; switch (loadType) { case 0: loadService = new ImageLoadServiceA(); break; case 1: loadService = new ImageLoadServiceB(); break; case 2: loadService = new ImageLoadServiceC(); break; } return loadService; } } // 使用 public void loadNetImage(int loadType) { ImageLoadFactory.create(loadType).loadImage(); }
可以看到,我們使用簡單工廠模式後,在處理新增其他載入方式的問題的時候,不會再去影響原有的載入類程式碼,如果新增一種載入方式的話,我們只需要新增 ImageLoadXXX
類,實現 loadImage()
載入方法,再修改工廠類 ImageLoadFactory
即可。
相信你也發現了,這個方式只能解決物件的建立問題,我們每次新增方式的時候都會新增一個類,而且需要對工廠類進行程式碼修改,顯然是違反了開閉原則。
策略模式
人生處處有策略,上面的不同的載入方式其實就是不同的「策略」。
策略模式是對 演算法的封裝,它將每一個演算法封裝到具有共同介面的獨立的類中,從而使得它們可以獨立變換。
策略模式的特點
- 是一種行為模式,對演算法封裝,使得客戶端獨立於各個策略;
- 擴充套件性強,新增策略無非就是新增一個具體的實現類而已,代價非常低;
策略模式的結構
策略模式做實現
要學習一個設計模式,先要學會臨摹,所以上面的需求,我們可以實現為:
- 定義抽象策略
public interface ImageLoadStrategy {
void loadImage() ;
}
- 定義具體的策略
// 具體載入類A
public class ImageLoadStrategyA implements ImageLoadStrategy {
@Override
public void loadImage() {
System.out.println("使用 A 載入框架");
}
}
//具體載入類B
public class ImageLoadStrategyB implements ImageLoadStrategy {
@Override
public void loadImage() {
System.out.println("使用 B 載入框架");
}
}
//具體載入類C
public class ImageLoadStrategyC implements ImageLoadStrategy {
@Override
public void loadImage() {
System.out.println("使用 C 載入框架");
}
}
- 定義上下文,選擇方式
public class ContextImageLoadStrategy {
private ImageLoadStrategy strategy ;
public ContextImageLoadStrategy(ImageLoadStrategy strategy){
this.strategy = strategy ;
}
public void loadImage(){
strategy.loadImage();
}
}
- 使用
public void loadImage(ImageLoadStrategy imageLoadStrategy){
ContextImageLoadStrategy contextStrategy = new ContextImageLoadStrategy(imageLoadStrategy);
contextStrategy.loadImage();
}
注意: 策略的核心不是如何實現演算法,而是如何更優雅的把這些演算法組織起來,讓客戶端非常好呼叫「雖然策略非常多,可以自由切換,但是同一時間客戶端只能呼叫一個策略,其實也很好理解,你不可能同時既坐飛機,又坐火車」。
策略模式的優點
- 策略類可以互相替換
由於策略類都實現同一個介面,因此他們能夠互相替換。 - 耦合度低,方便擴充套件
增加一個新的策略只需要新增一個具體的策略類即可,基本不需要改變原有的程式碼,符合開閉原則。 - 避免使用多重條件選擇語句(
if-else
或者switch
)。
策略模式的缺點
- 策略的增多會導致子類的也會變多。比如上方再增加載入方式必須增加類。
- 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。比如上方必須知道有哪些載入策略,這樣我們才能呼叫到正確的載入方式。
你有想到如何解決「客戶端必須知道所有的策略類」這個缺點麼?
策略模式的應用場景
- 同一個問題具有不同演算法時,即僅僅是具體的實現細節不同時,如各種排序演算法等等。
- 對客戶隱藏具體策略(演算法)的實現細節,彼此完全獨立;提高演算法的保密性與安全性。
- 一個類擁有很多行為,而又需要使用
if-else
或者switch
語句來選擇具體行為時。使用策略模式把這些行為獨立到具體的策略類中,可以避免多重選擇的結構。
原始碼中的策略模式
想必大家已經很清楚上面的策略模式了,下面原始碼中用到策略模式了嗎?
- Android 的動畫插值器;
- Android 中
ListView
的ArrayAdapter
、SimpleAdapter
;
寫在最後
總的來說,策略模式還算我們專案開發中會使用非常頻繁的模式,你學會了麼?如有疑問,請在評論區留言。