1. 策略模式原型舉例
現在要實現一個算稅策略,稅計算類型有價內稅和價外稅,將來可能會增加新的稅類型,初始設計類結構如下:

類 | 職責 |
---|---|
TaxStrategy | 稅策略接口 |
InterTaxStrategy | 價內稅策略,負責計算價內稅 |
OuterTaxStrategy | 價外稅策略,負責計算價外稅 |
TaxType | 稅類型定義,當前只有價內稅和價外稅 |
TaxStrategyFactory | 稅策略工廠,根據稅類型獲取不同的稅策略來算稅 |
這里還要注意:不管你是為了JAVA高薪還是愛好,記住:項目開發經驗永遠是核心,如果你沒有最新JAVA架構實戰視頻教程及大廠面試寶典,可以去小編的Java架構學習.裙 :七吧傘吧零而衣零傘 (數字的諧音)轉換下可以找到了,里面很多新JAVA架構項目教程,還可以跟老司機交流討教!
2. 代碼
2.1. 稅策略代碼
public interface TaxStrategy { double calc(long amount); } class InterTaxStrategy implements TaxStrategy { @Override public double calc(long amount) { final double taxRate = 0.2; // 獲取稅率 return amount * taxRate; } } class OuterTaxStrategy implements TaxStrategy { @Override public double calc(long amount) { final double taxRate = 0.2; // 獲取稅率 return amount / (1 + taxRate) * taxRate; } } // 稅類型定義 public enum TaxType { INTER, OUTER } 復制代碼
2.2. IF語句實現的稅策略工廠
// 稅策略工廠 public class TaxStrategyFactory { public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception { // 當增加新的稅類型時,需要修改代碼,同時會增加圈復雜度 if (taxType == TaxType.INTER) { return new InterTaxStrategy(); } else if (taxType == TaxType.OUTER) { return new OuterTaxStrategy(); } else { throw new Exception("The tax type is not supported."); } } } 復制代碼
可以看到,如果通過if語句來獲取不同的稅策略,當增加新的稅策略時就不得不修改已有代碼,當算稅方法很多時,就不那么好看,同時也增加了圈復雜度。
2.3. 首次優化 稅策略工廠中使用Map替代if
public class MapTaxStrategyFactory { // 存儲稅策略 static Map<TaxType, TaxStrategy> taxStrategyMap = new HashMap<>(); // 注冊默認稅策略 static { registerTaxStrategy(TaxType.INTER, new InterTaxStrategy()); registerTaxStrategy(TaxType.OUTER, new OuterTaxStrategy()); } // 提供稅注冊策略接口,外部只需要調用此接口接口新增稅策略,而無需修改策略工廠內部代碼 public static void registerTaxStrategy(TaxType taxType, TaxStrategy taxStrategy) { taxStrategyMap.put(taxType, taxStrategy); } // 通過map獲取稅策略,當增加新的稅策略時無需修改代碼,對修改封閉,對擴展開放,遵循開閉原則 public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception { // 當增加新的稅類型時,需要修改代碼,同時增加圈復雜度 if (taxStrategyMap.containsKey(taxType)) { return taxStrategyMap.get(taxType); } else { throw new Exception("The tax type is not supported."); } } } 復制代碼
可以看到,進化后IF語句沒有了,減少了圈復雜度,增加新的策略后只需調用策略注冊接口就好,不需要修改獲取稅策略的代碼。
2.4. 二次優化 策略自動注冊
在上面的實現中,要注冊新的稅策略,必須手動調用MapTaxStrategyFactory的注冊接口,這樣,每新增加一個稅策略都需要修改已有代碼,或者要找到一個合適的初始化調用點,去注冊稅策略,如何能完美的符合開閉原則,對修改關閉,對擴展開放呢?
再次優化后,類結構如下:

類 | 職責 |
---|---|
TaxStrategy | 稅策略接口,提供算稅接口,同時自注冊到稅策略工廠中 |
InterTaxStrategy | 價內稅策略,負責計算價內稅 |
OuterTaxStrategy | 價外稅策略,負責計算價外稅 |
TaxType | 稅類型定義,當前只有價內稅和價外稅 |
AutoRegisterTaxStrategyFactory | 稅策略工廠,根據稅類型獲取不同的稅策略來算稅,同時提供稅策略注冊接口 |
下面我看變化后的代碼:
2.4.1. 稅策略
public interface TaxStrategy { double calc(long amount); // 新增自注冊接口 void register(); } class InterTaxStrategy implements TaxStrategy { @Override public double calc(long amount) { final double taxRate = 0.2; // 獲取稅率 return amount * taxRate; } @Override public void register() { // 自己注冊到策略工廠中 AutoRegisterTaxStrategyFactory.registerTaxStrategy(TaxType.INTER, this); } } class OuterTaxStrategy implements TaxStrategy { @Override public double calc(long amount) { final double taxRate = 0.2; // 獲取稅率 return amount / (1 + taxRate) * taxRate; } @Override public void register() { // 自己注冊到策略工廠中 AutoRegisterTaxStrategyFactory.registerTaxStrategy(TaxType.OUTER, this); } } 復制代碼
2.4.2. 稅工廠
import java.util.*; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; public class AutoRegisterTaxStrategyFactory { // 存儲稅策略 static Map<TaxType, TaxStrategy> taxStrategyMap = new HashMap<>(); static { // 注冊稅策略 autoRegisterTaxStrategy(); } // 通過map獲取稅策略,當增加新的稅策略時無需修改代碼,對修改封閉,對擴展開放,遵循開閉原則 public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception { // 當增加新的稅類型時,需要修改代碼,同時增加圈復雜度 if (taxStrategyMap.containsKey(taxType)) { return taxStrategyMap.get(taxType); } else { throw new Exception("The tax type is not supported."); } } // 提供稅注冊策略接口,外部只需要調用此接口接口新增稅策略,而無需修改策略工廠內部代碼 public static void registerTaxStrategy(TaxType taxType, TaxStrategy taxStrategy) { taxStrategyMap.put(taxType, taxStrategy); } // 自動注冊稅策略 private static void autoRegisterTaxStrategy() { try { // 通過反射找到所有的稅策略子類進行注冊 Reflections reflections = new Reflections(new ConfigurationBuilder() .setUrls(ClasspathHelper.forPackage(TaxStrategy.class.getPackage().getName())) .setScanners(new SubTypesScanner())); Set<Class<? extends TaxStrategy>> taxStrategyClassSet = reflections.getSubTypesOf(TaxStrategy.class); if (taxStrategyClassSet != null) { for (Class<?> clazz: taxStrategyClassSet) { TaxStrategy taxStrategy = (TaxStrategy)clazz.newInstance(); // 調用稅策略的自注冊方法 taxStrategy.register(); } } } catch (InstantiationException | IllegalAccessException e) { // 自行定義異常處理 e.printStackTrace(); } } } 復制代碼
注:代碼中反射工具需要添加的依賴如下.
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
<optional>true</optional>
</dependency>
復制代碼
2.4.3. 使用
public class DecisionDemo { public static void main(String[] args) throws Exception { TaxStrategy taxStrategy = AutoRegisterTaxStrategyFactory.getTaxStrategy(TaxType.INTER); System.out.println(taxStrategy.calc(100)); } } 復制代碼
至此,當添加新的稅策略時,就完全不需要修改已有的稅策略工廠代碼,基本完美做到開閉原則,唯一需要修改的是稅類型定義。
2.5. 三次優化 通過注解減少耦合(網友辜圓圓建議)
基本思路是在稅策略上使用注解說明是哪種稅類型,在稅策略工廠中自動根據注解完成稅策略注冊,無需在每個稅策略中調用稅策略工廠的注冊接口。其類結構圖如下:

下面看看變化的代碼。
2.5.1. TaxTypeAnnotation.java
import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface TaxTypeAnnotation { TaxType taxType(); } 復制代碼
2.5.2. 策略類
稅策略去掉了注冊方法,添加TaxTypeAnnotation注解來識別是哪種稅類型。
public interface TaxStrategy { double calc(long amount); } @TaxTypeAnnotation(taxType = TaxType.INTER) class InterTaxStrategy implements TaxStrategy { @Override public double calc(long amount) { final double taxRate = 0.2; // 獲取稅率 return amount * taxRate; } } @TaxTypeAnnotation(taxType = TaxType.OUTER) class OuterTaxStrategy implements TaxStrategy { @Override public double calc(long amount) { final double taxRate = 0.2; // 獲取稅率 return amount / (1 + taxRate) * taxRate; } } 復制代碼
2.5.3. 工廠類
public class AnnotationTaxStrategyFactory { // 存儲稅策略 static Map<TaxType, TaxStrategy> taxStrategyMap = new HashMap<>(); static { registerTaxStrategy(); } // 通過map獲取稅策略,當增加新的稅策略時無需修改代碼,對修改封閉,對擴展開放,遵循開閉原則 public static TaxStrategy getTaxStrategy(TaxType taxType) throws Exception { // 當增加新的稅類型時,需要修改代碼,同時增加圈復雜度 if (taxStrategyMap.containsKey(taxType)) { return taxStrategyMap.get(taxType); } else { throw new Exception("The tax type is not supported."); } } // 自動注冊稅策略 private static void registerTaxStrategy() { try { // 通過反射找到所有的稅策略子類進行注冊 Reflections reflections = new Reflections(new ConfigurationBuilder() .setUrls(ClasspathHelper.forPackage(TaxStrategy.class.getPackage().getName())) .setScanners(new SubTypesScanner())); Set<Class<? extends TaxStrategy>> taxStrategyClassSet = reflections.getSubTypesOf(TaxStrategy.class); if (taxStrategyClassSet != null) { for (Class<?> clazz: taxStrategyClassSet) { // 找到稅類型注解,自動完成稅策略注冊 if (clazz.isAnnotationPresent(TaxTypeAnnotation.class)) { TaxTypeAnnotation taxTypeAnnotation = clazz.getAnnotation(TaxTypeAnnotation.class); TaxType taxType = taxTypeAnnotation.taxType(); taxStrategyMap.put(taxType, (TaxStrategy)clazz.newInstance()); } } } } catch (InstantiationException | IllegalAccessException e) { // 自行定義異常處理 e.printStackTrace(); } } } 復制代碼
此方式減少了稅策略和稅工廠的依賴,只需要關注自己的算法實現,解耦做得最好。
2.6. 終極優化 提煉通用設計模式
在軟件系統中,類似如上的策略算法會很多,不同的算稅策略,不同的加密策略,不同的XXX策略等等,這些可以統一再次抽象,提取出公共的策略接口,策略工廠類,這樣就不需要每種策略都有一套代碼實現,共用一套代碼足矣。
這個抽象出來的設計模式可以稱為自注冊策略模式,實際代碼就不寫了,留給大家自行思考完成(提示:使用泛型來抽象)。
3. 總結
注意:不管你是為了JAVA高薪還是愛好,記住:項目開發經驗永遠是核心,如果你沒有最新JAVA架構實戰視頻教程及大廠面試寶典,可以去小編的Java架構學習.裙 :七吧傘吧零而衣零傘 (數字的諧音)轉換下可以找到了,里面很多新JAVA架構項目教程,還可以跟老司機交流討教!
- Map替代if實現策略選擇,可提高擴展性,減少圈復雜度。
- 自注冊策略模式優雅的滿足了開閉原則,對修改封閉,對擴展開放。
- 越熟悉Java基礎特性越能想到更好的方案,例如在文中使用的注解特性。所以平時應多學習JAVA基礎特性,不要覺得已經夠用就不去了解新特性,新特性出來一定有它的優點,比如解決性能,優雅編碼,解耦等等。
本文的文字及圖片來源於網絡加上自己的想法,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理