一、背景1.1 反面教材
if (true) { if (true) { if (true) { if (true) { if (true) { if (true) { } } } } }}
2.2 親歷的重構
public Double commonMethod(Integer type, Double amount) { if (3 == type) { // 計算費用 if (true) { // 此處省略200行代碼,包含n個if-else,下同。。。 } return 0.00; } else if (2 == type) { // 計算費用 return 6.66; }else if (1 == type) { // 計算費用 return 8.88; }else if (0 == type){ return 9.99; } throw new IllegalArgumentException("please input right value");}
2.3 追根溯源
- 我們來分析下代碼多分支的原因
- 業務判斷
- 空值判斷
- 狀態判斷
- 如何處理呢?
- 在有多種算法相似的情況下,利用策略模式,把業務判斷消除,各子類實現同一個接口,只關注自己的實現(本文核心);
- 儘量把所有空值判斷放在外部完成,內部傳入的變量由外部接口保證不爲空,從而減少空值判斷(可參考如何從 if-else 的參數校驗中解放出來?);
- 把分支狀態信息預先緩存在Map裏,直接get獲取具體值,消除分支(本文也有體現)。
- 來看看簡化後的業務調用
CalculationUtil.getFee(type, amount)
serviceFeeHolder.getFee(type, amount)
二、通用部分2.1 需求概括
2.2 會員枚舉
用於維護會員類型。
public enum MemberEnum { ORDINARY_MEMBER(0, "普通會員"), JUNIOR_MEMBER(1, "初級會員"), INTERMEDIATE_MEMBER(2, "中級會員"), SENIOR_MEMBER(3, "高級會員"), ; int code; String desc; MemberEnum(int code, String desc) { this.code = code; this.desc = desc; } public int getCode() { return code; } public void setDesc(int code) { this.code = code; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; }}2.3 定義一個策略接口
- compute(Double amount):各計費規則的抽象
- getType():獲取枚舉中維護的會員級別
public interface FeeService { /** * 計費規則 * @param amount 會員的交易金額 * @return */ Double compute(Double amount); /** * 獲取會員級別 * @return */ Integer getType();}三、非框架實現3.1 項目依賴<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope></dependency>3.2 不同計費規則的實現
- 普通會員計費規則
public class OrdinaryMember implements FeeService { /** * 計算普通會員所需繳費的金額 * @param amount 會員的交易金額 * @return */ @Override public Double compute(Double amount) { // 具體的實現根據業務需求修改 return 9.99; } @Override public Integer getType() { return MemberEnum.ORDINARY_MEMBER.getCode(); }}
- 初級會員計費規則
public class JuniorMember implements FeeService { /** * 計算初級會員所需繳費的金額 * @param amount 會員的交易金額 * @return */ @Override public Double compute(Double amount) { // 具體的實現根據業務需求修改 return 8.88; } @Override public Integer getType() { return MemberEnum.JUNIOR_MEMBER.getCode(); }}
- 中級會員計費規則
public class IntermediateMember implements FeeService { /** * 計算中級會員所需繳費的金額 * @param amount 會員的交易金額 * @return */ @Override public Double compute(Double amount) { // 具體的實現根據業務需求修改 return 6.66; } @Override public Integer getType() { return MemberEnum.INTERMEDIATE_MEMBER.getCode(); }}
- 高級會員計費規則
public class SeniorMember implements FeeService { /** * 計算高級會員所需繳費的金額 * @param amount 會員的交易金額 * @return */ @Override public Double compute(Double amount) { // 具體的實現根據業務需求修改 return 0.01; } @Override public Integer getType() { return MemberEnum.SENIOR_MEMBER.getCode(); }}3.3 核心工廠
public class ServiceFeeFactory { private Map<Integer, FeeService> map; public ServiceFeeFactory() { // 該工廠管理所有的策略接口實現類 List<FeeService> feeServices = new ArrayList<>(); feeServices.add(new OrdinaryMember()); feeServices.add(new JuniorMember()); feeServices.add(new IntermediateMember()); feeServices.add(new SeniorMember()); // 把所有策略實現的集合List轉爲Map map = new ConcurrentHashMap<>(); for (FeeService feeService : feeServices) { map.put(feeService.getType(), feeService); } } /** * 靜態內部類單例 */ public static class Holder { public static ServiceFeeFactory instance = new ServiceFeeFactory(); } /** * 在構造方法的時候,初始化好 需要的 ServiceFeeFactory * @return */ public static ServiceFeeFactory getInstance() { return Holder.instance; } /** * 根據會員的級別type 從map獲取相應的策略實現類 * @param type * @return */ public FeeService get(Integer type) { return map.get(type); }}3.4 工具類
public class CalculationUtil { /** * 暴露給用戶的的計算方法 * @param type 會員級別標示(參見 MemberEnum) * @param money 當前交易金額 * @return 該級別會員所需繳納的費用 * @throws IllegalArgumentException 會員級別輸入錯誤 */ public static Double getFee(int type, Double money) { FeeService strategy = ServiceFeeFactory.getInstance().get(type); if (strategy == null) { throw new IllegalArgumentException("please input right value"); } return strategy.compute(money); }}
3.5 測試public class DemoTest { @Test public void test() { Double fees = upMethod(1,20000.00); System.out.println(fees); // 會員級別超範圍,拋 IllegalArgumentException Double feee = upMethod(5, 20000.00); } public Double upMethod(Integer type, Double amount) { // getFee()是暴露給用戶的的計算方法 return CalculationUtil.getFee(type, amount); }}
- 執行結果
8.88java.lang.IllegalArgumentException: please input right value四、Spring Boot 實現
上述方法無非是藉助策略模式+工廠模式+單例模式實現,但是實際場景中,我們都已經集成了Spring Boot,這一段就看一下如何藉助Spring Boot更簡單實現本次的優化。
4.1 項目依賴<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>4.2 不同計費規則的實現
- 普通會員計費規則
@Componentpublic class OrdinaryMember implements FeeService { /** * 計算普通會員所需繳費的金額 * @param amount 會員的交易金額 * @return */ @Override public Double compute(Double amount) { // 具體的實現根據業務需求修改 return 9.99; } @Override public Integer getType() { return MemberEnum.ORDINARY_MEMBER.getCode(); }}
- 初級會員計費規則
@Componentpublic class JuniorMember implements FeeService { /** * 計算初級會員所需繳費的金額 * @param amount 會員的交易金額 * @return */ @Override public Double compute(Double amount) { // 具體的實現根據業務需求修改 return 8.88; } @Override public Integer getType() { return MemberEnum.JUNIOR_MEMBER.getCode(); }}
- 中級會員計費規則
@Componentpublic class IntermediateMember implements FeeService { /** * 計算中級會員所需繳費的金額 * @param amount 會員的交易金額 * @return */ @Override public Double compute(Double amount) { // 具體的實現根據業務需求修改 return 6.66; } @Override public Integer getType() { return MemberEnum.INTERMEDIATE_MEMBER.getCode(); }}
- 高級會員計費規則
@Componentpublic class SeniorMember implements FeeService { /** * 計算高級會員所需繳費的金額 * @param amount 會員的交易金額 * @return */ @Override public Double compute(Double amount) { // 具體的實現根據業務需求修改 return 0.01; } @Override public Integer getType() { return MemberEnum.SENIOR_MEMBER.getCode(); }}4.3 別名轉換
思考:程序如何通過一個標識,怎麼識別解析這個標識,找到對應的策略實現類?
- application.yml
alias: aliasMap: first: ordinaryMember second: juniorMember third: intermediateMember fourth: seniorMember
- AliasEntity.java
@Component@EnableConfigurationProperties@ConfigurationProperties(prefix = "alias")public class AliasEntity { private HashMap<String, String> aliasMap; public HashMap<String, String> getAliasMap() { return aliasMap; } public void setAliasMap(HashMap<String, String> aliasMap) { this.aliasMap = aliasMap; } /** * 根據描述獲取該會員對應的別名 * @param desc * @return */ public String getEntity(String desc) { return aliasMap.get(desc); }}
該類爲了便於讀取配置,因爲存入的是Map的key-value值,key存的是描述,value是各級別會員Bean的別名。
4.4 策略工廠@Componentpublic class ServiceFeeHolder { /** * 將 Spring 中所有實現 ServiceFee 的接口類注入到這個Map中 */ @Resource private Map<String, FeeService> serviceFeeMap; @Resource private AliasEntity aliasEntity; /** * 獲取該會員應當繳納的費用 * @param desc 會員標誌 * @param money 交易金額 * @return * @throws IllegalArgumentException 會員級別輸入錯誤 */ public Double getFee(String desc, Double money) { return getBean(desc).compute(money); } /** * 獲取會員標誌(枚舉中的數字) * @param desc 會員標誌 * @return * @throws IllegalArgumentException 會員級別輸入錯誤 */ public Integer getType(String desc) { return getBean(desc).getType(); } private FeeService getBean(String type) { // 根據配置中的別名獲取該策略的實現類 FeeService entStrategy = serviceFeeMap.get(aliasEntity.getEntity(type)); if (entStrategy == null) { // 找不到對應的策略的實現類,拋出異常 throw new IllegalArgumentException("please input right value"); } return entStrategy; }}
- 將 Spring中所有 ServiceFee.java 的實現類注入到Map中,不同策略通過其不同的key獲取其實現類;
- 找不到對應的策略的實現類,拋出IllegalArgumentException異常。
4.5 測試@SpringBootTest@RunWith(SpringRunner.class)public class DemoTest { @Resource ServiceFeeHolder serviceFeeHolder; @Test public void test() { // 計算應繳納費用 System.out.println(serviceFeeHolder.getFee("second", 1.333)); // 獲取會員標誌 System.out.println(serviceFeeHolder.getType("second")); // 會員描述錯誤,拋 IllegalArgumentException System.out.println(serviceFeeHolder.getType("zero")); }}
- 執行結果
8.881java.lang.IllegalArgumentException: please input right value五、總結
- 系統中有很多類,而他們的區別僅僅在於他們的行爲不同。
- 一個系統需要動態地在幾種算法中選擇一種。
5.1 策略模式角色
- Context: 環境類
Context叫做上下文角色,起承上啓下封裝作用,屏蔽高層模塊對策略、算法的直接訪問,封裝可能存在的變化,對應本文的ServiceFeeFactory.java。
- Strategy: 抽象策略類
定義算法的接口,對應本文的FeeService.java。
- ConcreteStrategy: 具體策略類
實現具體策略的接口,對應本文的OrdinaryMember.java/JuniorMember.java/IntermediateMember.java/SeniorMember.java。