JAVA編程思想(三)去掉彆扭的if,自注冊策略模式優雅知足開閉原則

Java極客  |  做者  /  鏗然一葉
這是Java極客的第 65 篇原創文章

相關閱讀:java

JAVA編程思想(一)經過依賴注入增長擴展性
JAVA編程思想(二)如何面向接口編程
JAVA編程思想(四)Builder模式經典範式以及和工廠模式如何選?
JAVA基礎(三)ClassLoader實現熱加載
Java併發編程入門(十一)限流場景和Spring限流器實現
HikariPool源碼(二)設計思想借鑑
人在職場(一)IT大廠生存法則算法


1. 策略模式原型舉例

如今要實現一個算稅策略,稅計算類型有價內稅和價外稅,未來可能會增長新的稅類型,初始設計類結構以下:編程

職責
TaxStrategy 稅策略接口
InterTaxStrategy 價內稅策略,負責計算價內稅
OuterTaxStrategy 價外稅策略,負責計算價外稅
TaxType 稅類型定義,當前只有價內稅和價外稅
TaxStrategyFactory 稅策略工廠,根據稅類型獲取不一樣的稅策略來算稅

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的註冊接口,這樣,每新增長一個稅策略都須要修改已有代碼,或者要找到一個合適的初始化調用點,去註冊稅策略,如何能完美的符合開閉原則,對修改關閉,對擴展開放呢dom

再次優化後,類結構以下:ide

職責
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();
        }
    }
}
複製代碼

注:代碼中反射工具須要添加的依賴以下.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>
複製代碼

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. 總結

  1. Map替代if實現策略選擇,可提升擴展性,減小圈複雜度
  2. 自注冊策略模式優雅的知足了開閉原則,對修改封閉,對擴展開放。
  3. 越熟悉Java基礎特性越能想到更好的方案,例如在文中使用的註解特性。因此平時應多學習JAVA基礎特性,不要以爲已經夠用就不去了解新特性,新特性出來必定有它的優勢,好比解決性能,優雅編碼,解耦等等。

end.


<--閱過留痕,左邊點贊!

相關文章
相關標籤/搜索