開局還是那種圖,最近策略模式貌似很火,各位客官往下看...
策略模式到底是什麼?
前面我們其實已經將結構型模式講解完了,剩下的全都是行為型模式,三種模式的區分:
- 建立型模式:如何建立一個物件
- 結構型模式:物件內部的構造是如何構造的
- 行為型模式:物件是如何執行(可以做什麼)
而提到策略模式,我們該如何理解呢?
-
從北京到上海,可以坐飛機,也可以坐動車,也可以坐綠皮,甚至可以騎自行車,這些不同的方式,就是策略。
-
一件商品,可以直接打 5 折,也可以加 100 再打 6 折,也可以打 5.5 折之後返回現金券,這些也是策略。
-
帶了 200 去超市買東西,可以買零食,也可以買生活用品,也可以啥也不買,這些也是策略。
上面的例子,其實我們可以發現不同的策略其實是可以互換的,甚至策略細節之間可以混搭,那麼很多時候我們為了方便擴充,方便替換策略,對呼叫方遮蔽不同的策略細節,就可以使用策略模式。倘若所有的策略全部寫在一個類裡面,可以是可以,這樣維護程式碼會越來越複雜,維護只會越來越困難,裡面會充斥著各種if-else
,演算法如果日益複雜,動一發而牽全身,那就只剩下提桶跑路了。
策略模式是指有一定行動內容的相對穩定的策略名稱。策略模式在古代中又稱“計策”,簡稱“計”,如《漢書·高帝紀上》:“漢王從其計”。這裡的“計”指的就是計謀、策略。策略模式具有相對穩定的形式,如“避實就虛”、“出奇制勝”等。一定的策略模式,既可應用於戰略決策,也可應用於戰術決策;既可實施於大系統的全域性性行動,也可實施於大系統的區域性性行動。
再說一個打工人都知道的例子,比如每個人都要交個人所得稅,我們知道個人所得稅的計算方式是很複雜的,稅法計算就是一個行為,而可能存在不同實現演算法,那每一種計算演算法都是一種策略。
這些策略可能隨時被替換掉,那麼我們為了好替換,相互隔離,就定義一系列演算法,封裝起來,這就叫策略模式。
策略模式的角色
策略模式一般有三種角色:
- 抽象的策略類(
Strategy
):將策略的行為抽象出公共介面 - 具體的策略類(
ConcreteStrategy
):以Strategy
介面實現某些具體的演算法,具體的策略可能多個,也可以相互替換 - 上下文(
Context
): 一般是封裝了策略,以及策略的使用方法,對上層呼叫遮蔽了細節,直接呼叫即可。
策略模式的demo
建立一個抽象的策略介面:
public interface Strategy {
int doStrategy();
}
建立 2 個具體的策略類,分別執行策略:
public class ConcreteStrategy1 implements Strategy{
@Override
public int doStrategy() {
System.out.println("執行策略1...");
return 1;
}
}
public class ConcreteStrategy2 implements Strategy{
@Override
public int doStrategy() {
System.out.println("執行策略2...");
return 2;
}
}
建立上下文類,封裝策略類:
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy(){
strategy.doStrategy();
}
}
測試類:
public class Test {
public static void main(String[] args) {
Context context1 = new Context(new ConcreteStrategy1());
context1.executeStrategy();
context1 = new Context(new ConcreteStrategy2());
context1.executeStrategy();
}
}
測試結果:
執行策略1
執行策略2
可以看到只要使用不同的策略,就可以執行不同的結果。
分紅包策略實戰
比如,工作中有涉及到分配的策略,場景就是有紅包,分配給若干個人,但是會有不同分配策略。這裡不同的分配策略其實就是用策略模式來實現。可以隨機分配,也可以平均分配,還可以根據各種權重來分配等等。這裡只演示兩種分配:
首先定義一個紅包類,包含紅包金額(我們以分為單位,儲存整數,避免金額小數不準確問題)
public class RedPackage {
public int remainSize;
public int remainMoney;
public RedPackage(int remainSize, int remainMoney) {
this.remainSize = remainSize;
this.remainMoney = remainMoney;
}
}
定義一個分配策略的抽象類:
import java.util.List;
public interface AllocateStrategy {
List<Integer> allocate(RedPackage redPackage);
}
平均分配的時候,如果不能平均,那麼第一位會有所增減:
import java.util.ArrayList;
import java.util.List;
public class AverageAllocateStrategy implements AllocateStrategy {
@Override
public List<Integer> allocate(RedPackage redPackage) {
List<Integer> results = new ArrayList<>();
Integer sum = redPackage.remainMoney;
Integer average = sum / redPackage.remainSize;
for (int i = 0; i < redPackage.remainSize; i++) {
sum = sum - average;
results.add(average);
}
if (sum != 0) {
results.set(0, results.get(0) + sum);
}
return results;
}
}
隨機分配的時候,我們將每一份隨機新增到紅包裡面去,但是這樣不能保證每一份都有金額(注意這個不要在生產使用,僅限測試)
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class RandomAllocateStrategy implements AllocateStrategy {
@Override
public List<Integer> allocate(RedPackage redPackage) {
return ranRedPackage(redPackage.remainSize, redPackage.remainMoney);
}
public List<Integer> ranRedPackage(Integer count, Integer money) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
result.add(0);
}
for (int i = 1; i <= money; i++) {
int n = new Random().nextInt(count);
result.set(n, result.get(n) + 1);
}
return result;
}
}
定義上下文類,封裝不同的策略:
import java.util.List;
public class Context {
private AllocateStrategy strategy;
public Context(AllocateStrategy strategy) {
this.strategy = strategy;
}
public List<Integer> executeStrategy(RedPackage redPackage){
return strategy.allocate(redPackage);
}
}
測試類:
import java.util.List;
public class Test {
public static void main(String[] args) {
RedPackage redPackage = new RedPackage(10,102);
Context context = new Context(new RandomAllocateStrategy());
List<Integer> list =context.executeStrategy(redPackage);
list.forEach(l-> System.out.print(l+" "));
System.out.println("");
context = new Context(new AverageAllocateStrategy());
list =context.executeStrategy(redPackage);
list.forEach(l-> System.out.print(l+" "));
}
}
可以看到分配的金額確實會隨著策略類的不同發生不同的變化:
9 10 16 8 14 8 7 15 9 6
12 10 10 10 10 10 10 10 10 10
注意這個不能在生產使用!!!
優缺點
策略模式的優點:
- 消除
if-else
語句,不同策略相互隔離,方便維護 - 切換自由
- 擴充性好,實現介面即可
策略模式的缺點:
- 策略一旦數量過多,維護也會相對比較難,複用程式碼比較難
- 所有的策略類都對外暴露了,雖然一般通過
Context
上下文呼叫
策略模式比較常用,可能有些同學會混淆策略模式和狀態模式:
相對於狀態模式:策略模式只會執行一次方法,而狀態模式會隨著狀態變化不停的執行狀態變更方法。舉個例子,我們從A
地方去B
地方,策略就是飛機或者火車,狀態模式則是可能到某一個地方,換了一種交通方式,到另外一個地方又換了一種交通方式。
小結
策略模式比較常用,核心就是隔離不同的策略,將具體的演算法封裝在策略裡面,抽象出一個策略抽象類,把不同具體策略類注入到上下文類中,達到選擇不同策略的目的。
但是如果策略很多,需要考慮複用,策略類對外暴露的時候需要考慮濫用風險,會破壞封裝性。
【作者簡介】:
秦懷,公眾號【秦懷雜貨店】作者,個人網站:http://aphysia.cn,技術之路不在一時,山高水長,縱使緩慢,馳而不息。
設計模式系列:
- 設計模式【1】-- 單例模式到底幾種寫法?
- 設計模式【1.1】-- 你想如何破壞單例模式?
- 設計模式【1.2】-- 列舉式單例有那麼好用麼?
- 設計模式【1.3】-- 為什麼餓漢式單例是執行緒安全的?
- 設計模式【2】-- 簡單工廠模式瞭解一下?
- 設計模式【2.1】-- 簡單工廠模式怎麼演變成工廠方法模式?
- 設計模式【2.2】-- 工廠模式怎麼演變成抽象工廠模式?
- 設計模式【3.1】-- 淺談代理模式之靜態、動態、cglib代理
- 設計模式【3.2】-- JDK動態代理原始碼分析有多香?
- 設計模式【3.3】-- CGLIB動態代理原始碼解讀
- 設計模式【4】-- 建造者模式詳解
- 設計模式【5】-- 原型模式
- 設計模式【6.1】-- 初探介面卡模式
- 設計模式【6.2】-- 再聊聊介面卡模式
- 設計模式【7】-- 探索一下橋接模式
- 設計模式【8】-- 手工耿教我寫裝飾器模式
- 設計模式【9】-- 外觀模式?沒那麼高大上
- 設計模式【10】-- 順便看看享元模式
- 設計模式【11】-- 搞定組合模式