转载

JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则

JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
Java极客  |  作者  /  铿然一叶
这是 Java极客 的第 65 篇原创文章

相关阅读:

JAVA编程思想(一)通过依赖注入增加扩展性

JAVA编程思想(二)如何面向接口编程

JAVA基础(三)ClassLoader实现热加载

HikariPool源码(二)设计思想借鉴

1. 策略模式原型举例

现在要实现一个算税策略,税计算类型有价内税和价外税,将来可能会增加新的税类型,初始设计类结构如下:

JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
职责
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的注册接口,这样,每新增加一个税策略都需要修改已有代码,或者要找到一个合适的初始化调用点,去注册税策略,如何能完美的符合开闭原则,对修改关闭,对扩展开放呢?

再次优化后,类结构如下:

JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
职责
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. 终极优化 提炼通用设计模式

在软件系统中,类似如上的策略算法会很多,不同的算税策略,不同的加密策略,不同的XXX策略等等,这些可以统一再次抽象,提取出公共的策略接口,策略工厂类,这样就不需要每种策略都有一套代码实现,共用一套代码足矣。

这个抽象出来的设计模式可以称为自注册策略模式,实际代码就不写了,留给大家自行思考完成(提示:使用泛型来抽象)。

原文  https://juejin.im/post/5eabb10ff265da7be52e8c05
正文到此结束
Loading...