電商平臺為了增加銷量經常搞一些活動,比如 618、雙十一,還有一些節假日活動,根據銷量的變化又經常更新不同的活動。最開始為了增加銷量,全場都六折:
// 打六折
public BigDecimal sixDiscount(BigDecimal amount) {
BigDecimal discount = BigDecimal.valueOf(0.6);
return amount.multiply(discount);
}
促銷了幾天之後,發現銷量上去了,但是利潤又減少了,又改成打八折了:
// 打八折
public BigDecimal eightDiscount(BigDecimal amount) {
BigDecimal discount = BigDecimal.valueOf(0.8);
return amount.multiply(discount);
}
再過幾個月,活動每週都可能會改變,類似這週六折,下週八折,每次修改活動,就要改程式碼,就比較繁瑣。後面就根據引數來選擇不同的活動:
// 根據不同的type,選擇不同的打折方案
public BigDecimal simpleDiscount(int type,BigDecimal amount) {
if (type == 1) {
amount = amount.multiply(BigDecimal.valueOf(0.6));
} else if (type == 2) {
amount = amount.multiply(BigDecimal.valueOf(0.8));
}
return amount;
}
後面不增加活動方案的前提下,改變方案只需要前端選擇對應的方案即可。但如果每次新增程式碼,還是需要更新程式碼,還需要更多繁瑣的 if-else 判斷:
// 根據不同的type,選擇不同的打折方案
public BigDecimal discount(int type,BigDecimal amount) {
if (type == 1) {
amount = amount.multiply(BigDecimal.valueOf(0.6));
} else if (type == 2) {
amount = amount.multiply(BigDecimal.valueOf(0.8));
} else if (type == 3) {
// 滿100減20,滿200減50
} else if (type == 4) {
// 滿500減70
} else if (type == 5) {
// 滿1000減兩百
}
// 更多的方案 .....
return amount;
}
上面的方法只是一些簡單的打折方案,實際上的打折方案更復雜,一個打折方案程式碼至少要十多行,而上面十多個方案,程式碼顯得非常的臃腫。有如下幾個缺點:
解決上面的幾個問題一個比較好的方案就是使用策略模式,策略模式可以避免繁瑣的 if-else 分支判斷,同時降低程式碼的耦合度,增強程式碼的可讀性。
定義一系列的演演算法,把每個演演算法封裝起來, 並且使它們可相互替換。根據呼叫者傳參來呼叫具體的演演算法。將策略的定義、建立和使用三個部分解耦。下面就上面的打折方案的方法使用策略模式進行改造。
策略的定義包含一個策略介面和一組實現這個介面的策略類,所有的策略類都實現相同的介面。
介面和實現類如下:
// 建立一個介面
public interface Strategy {
BigDecimal discount(BigDecimal amount);
}
// 打六折
public class SixDiscountStrategy implements Strategy{
@Override
public BigDecimal discount(BigDecimal amount) {
return amount = amount.multiply(BigDecimal.valueOf(0.6));
}
}
// 打八折
public class EightDiscountStrategy implements Strategy{
@Override
public BigDecimal discount(BigDecimal amount) {
return amount = amount.multiply(BigDecimal.valueOf(0.8));
}
}
// 滿100減20,滿200減50
public class FirstDiscountStrategy implements Strategy{
@Override
public BigDecimal discount(BigDecimal amount) {
// 滿100減20,滿200減50
// 省略具體演演算法....
return amount;
}
}
將上面一個幾百行的方法,拆分成一個一個小的類。類的數量變多了,但是程式碼也更加簡潔了。
策略模式包含一組策略,一般通過型別 type 來選擇建立哪個策略類。將建立策略類的程式碼封裝成一個工具類,通過 type 直接呼叫:
// 策略工具類
public class Context {
public static Strategy operation(int type) {
if (type == 1) {
return new SixDiscountStrategy();
} else if (type == 2) {
return new EightDiscountStrategy();
} else if (type == 3) {
return new FirstDiscountStrategy();
}
throw new IllegalArgumentException("not fond strategy");
}
}
以上根據不同的 type 呼叫不同的策略類,在執行對應的方法。呼叫策略方法就簡單多了,為了方便測試,直接使用 mian 方法呼叫:
public static void main(String[] args) {
int type = 1;
Strategy strategy = Context.operation(type);
BigDecimal sixDiscount = strategy.discount(BigDecimal.valueOf(100));
System.out.println("六折優惠價格:" + sixDiscount);
}
可能細心的同學發現,還是有很多 if-else 的程式碼,每次新增一個活動,還需要在 Context 多寫一個判斷。如果策略類是無狀態的,可以被共用,那就不需要每次呼叫都建立一個新的策略物件,實現將建立好的物件快取到 map 集合中,呼叫的時候直接,同時也不需要寫 if-else 判斷條件,優化程式碼如下:
private static Map<Integer,Strategy> map = new HashMap<>();
static {
map.put(1,new SixDiscountStrategy());
map.put(2,new EightDiscountStrategy());
map.put(3,new FirstDiscountStrategy());
}
public static Strategy operation(int type) {
Strategy strategy = map.get(type);
if (strategy == null) {
throw new IllegalArgumentException("not fond strategy");
}
return strategy;
}
public static void main(String[] args) {
// 六折優惠
int type = 1;
Strategy strategy = Context.operation(type);
BigDecimal sixDiscount = strategy.discount(BigDecimal.valueOf(100));
System.out.println("六折優惠價格:" + sixDiscount);
}
重構之後的程式碼就沒有 if-else 分支語句了,主要是利用了策略模式和 Map 集合,通過 type 直接獲取 Map 集合上對應的策略類,從而很好的規避了 if-else 判斷。通過查表法代替 if-else 判斷。
將程式碼重構之後,程式碼也不會顯得臃腫和複雜,有如下幾個好處:
本文先從電商專案的一個複雜多變的優惠券活動上講起,優惠券的活動複雜多變,經常要搞不同的活動,可能每天也是不同的活動。針對複雜多變的需求,程式碼數量比較龐大和複雜,程式碼可讀性差,耦合度高。所以就需要使用一個設計模式簡化程式碼,減少程式碼改動量,增加程式碼的可讀性。策略模式就應允而生。