相關閱讀:java
JAVA編程思想(一)經過依賴注入增長擴展性
JAVA編程思想(二)如何面向接口編程
JAVA編程思想(四)Builder模式經典範式以及和工廠模式如何選?
JAVA基礎(三)ClassLoader實現熱加載
Java併發編程入門(十一)限流場景和Spring限流器實現
HikariPool源碼(二)設計思想借鑑
人在職場(一)IT大廠生存法則算法
如今要實現一個算稅策略,稅計算類型有價內稅和價外稅,未來可能會增長新的稅類型,初始設計類結構以下:編程
類 | 職責 |
---|---|
TaxStrategy | 稅策略接口 |
InterTaxStrategy | 價內稅策略,負責計算價內稅 |
OuterTaxStrategy | 價外稅策略,負責計算價外稅 |
TaxType | 稅類型定義,當前只有價內稅和價外稅 |
TaxStrategyFactory | 稅策略工廠,根據稅類型獲取不一樣的稅策略來算稅 |
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
}
複製代碼
// 稅策略工廠
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語句來獲取不一樣的稅策略,當增長新的稅策略時就不得不修改已有代碼,當算稅方法不少時,就不那麼好看,同時也增長了圈複雜度。設計模式
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語句沒有了,減小了圈複雜度,增長新的策略後只需調用策略註冊接口就好,不須要修改獲取稅策略的代碼。併發
在上面的實現中,要註冊新的稅策略,必須手動調用MapTaxStrategyFactory的註冊接口,這樣,每新增長一個稅策略都須要修改已有代碼,或者要找到一個合適的初始化調用點,去註冊稅策略,如何能完美的符合開閉原則,對修改關閉,對擴展開放呢?dom
再次優化後,類結構以下:ide
類 | 職責 |
---|---|
TaxStrategy | 稅策略接口,提供算稅接口,同時自注冊到稅策略工廠中 |
InterTaxStrategy | 價內稅策略,負責計算價內稅 |
OuterTaxStrategy | 價外稅策略,負責計算價外稅 |
TaxType | 稅類型定義,當前只有價內稅和價外稅 |
AutoRegisterTaxStrategyFactory | 稅策略工廠,根據稅類型獲取不一樣的稅策略來算稅,同時提供稅策略註冊接口 |
下面我看變化後的代碼:工具
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);
}
}
複製代碼
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();
}
}
}
複製代碼
注:代碼中反射工具須要添加的依賴以下.post
<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>
複製代碼
public class DecisionDemo {
public static void main(String[] args) throws Exception {
TaxStrategy taxStrategy = AutoRegisterTaxStrategyFactory.getTaxStrategy(TaxType.INTER);
System.out.println(taxStrategy.calc(100));
}
}
複製代碼
至此,當添加新的稅策略時,就徹底不須要修改已有的稅策略工廠代碼,基本完美作到開閉原則,惟一須要修改的是稅類型定義。性能
基本思路是在稅策略上使用註解說明是哪一種稅類型,在稅策略工廠中自動根據註解完成稅策略註冊,無需在每一個稅策略中調用稅策略工廠的註冊接口。其類結構圖以下:
下面看看變化的代碼。
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface TaxTypeAnnotation {
TaxType taxType();
}
複製代碼
稅策略去掉了註冊方法,添加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;
}
}
複製代碼
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();
}
}
}
複製代碼
此方式減小了稅策略和稅工廠的依賴,只須要關注本身的算法實現,解耦作得最好。
在軟件系統中,相似如上的策略算法會不少,不一樣的算稅策略,不一樣的加密策略,不一樣的XXX策略等等,這些能夠統一再次抽象,提取出公共的策略接口,策略工廠類,這樣就不須要每種策略都有一套代碼實現,共用一套代碼足矣。
這個抽象出來的設計模式能夠稱爲自注冊策略模式,實際代碼就不寫了,留給你們自行思考完成(提示:使用泛型來抽象)。
- Map替代if實現策略選擇,可提升擴展性,減小圈複雜度。
- 自注冊策略模式優雅的知足了開閉原則,對修改封閉,對擴展開放。
- 越熟悉Java基礎特性越能想到更好的方案,例如在文中使用的註解特性。因此平時應多學習JAVA基礎特性,不要以爲已經夠用就不去了解新特性,新特性出來必定有它的優勢,好比解決性能,優雅編碼,解耦等等。
end.