优化过长的“分支代码”

Posted on Aug 5, 2021

基于多态

假设有一段“分支代码”(片段 1):

// 可能的 Case
CaseEnum caseEnum = getRandomCase();
if (CaseEnum.AA.equals(caseEnum)) {
    // ...
} else if (CaseEnum.BB.equals(caseEnum)) {
    // ...
} else if (CaseEnum.CC.equals(caseEnum)) {
    // ...
}

其等效代码类似于(片段 2):

// 可能的 Case
CaseEnum caseEnum = getRandomCase();
for (CaseHandler handler : caseHandlerFactory.getHandlers()) {
    if (handler.match(caseEnum)) {
        handler.handle();
        break;
    }
}

当增加分支的时候,片段 1 需要在尾部追加代码,而片段 2 无需变更,前提是片段 2 依赖更多的类。

注意:片段 2 在最坏情况下的运行时间的增长数量级是 N。

如何实现?

(一)CaseHandler。

public interface CaseHandler {
    /**
     * 匹配 Case。
     *
     * @param caseEnum Case 枚举
     * @return 是否
     */
    boolean match(CaseEnum caseEnum);

    /**
     * 业务处理。
     */
    void handle();
}

(二)CaseHandler 实现之一。

public class AaHandler implements CaseHandler {

    @Override
    public boolean match(CaseEnum caseEnum) {
        return CaseEnum.AA.equals(caseEnum);
    }

    @Override
    public void handle() {
        // ...
    }
}

(三)CaseHandler 工厂。

public class CaseHandlerFactory {
    /**
     * handler name to handler
     */
    private final Map<String, CaseHandler> name2Handler;

    public CaseHandlerFactory() {
        this.name2Handler = new LinkedHashMap<>();
    }

    public CaseHandlerFactory addHandler(CaseHandler handler) {
        if (Objects.isNull(handler)) {
            return this;
        }
        name2Handler.put(handler.getClass().getSimpleName(), handler);
        return this;
    }

    public Collection<CaseHandler> getHandlers() {
        return name2Handler.values();
    }
}

(四)初始化 CaseHandler 工厂。

final CaseHandlerFactory caseHandlerFactory = new CaseHandlerFactory()
            .addHandler(new AaHandler())
            .addHandler(new BbHandler())
            .addHandler(new CcHandler())
            ;

依赖注入

基于 Spring IoC,自动实例化 CaseHandler 工厂。

(一)将 CaseHandler 的所有实现声明为 Spring Bean。

@Component
public class AaHandler implements CaseHandler {

    @Override
    public boolean match(CaseEnum caseEnum) {
        return CaseEnum.AA.equals(caseEnum);
    }

    @Override
    public void handle() {
        // ...
    }
}

(二)CaseHandlerFactory 构造器的依赖注入。

@Component
public class CaseHandlerFactory {
    /**
     * handler name to handler
     */
    private final Map<String, CaseHandler> name2Handler;

    @Autowired
    public CaseHandlerFactory(Collection<CaseHandler> handlers) {
        Assert.notNull(handlers, "handlers must not be null");
        this.name2Handler = new LinkedHashMap<>(handlers.size());
        handlers.forEach(this::addHandler);
    }

    public void addHandler(CaseHandler handler) {
        if (Objects.isNull(handler)) {
            return;
        }
        name2Handler.put(handler.getClass().getSimpleName(), handler);
    }

    public Collection<CaseHandler> getHandlers() {
        return name2Handler.values();
    }
}

(三)引用 CaseHandlerFactory。

@Resource
private CaseHandlerFactory caseHandlerFactory;

策略模式

基于 Spring Boot 开发 Web 程序时,有时会遇到需要根据不同的类型或策略(strategy)做不同的事情,例如:

@RestController
@RequestMapping("/some")
public class SomeController {
    @Resource
    private SomeService someService;

    @PostMapping("/doSomething")
    public ResponseEntity<?> doSomething(@RequestBody Some some) {
        someService.doSomething(some);
        return ResponseEntity.ok().build();
    }
}
@Service
public class SomeService {

    public void doSomething(Some some) {
        StrategyEnum strategyEnum = some.getStrategyEnum();
        Assert.notNull(strategyEnum, "strategyEnum must not be null");
        switch (strategyEnum) {
            case Strategy1:
                // ...
                break;
            case Strategy2:
                // ...
                break;
            case Strategy3:
                // ...
                break;
            default:
                // ...
        }
    }
}

使用策略模式可以优化过长的“分支代码”。

W3sDesign_Strategy_Design_Pattern_UML

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

大同小异,编写策略类和策略工厂类。

(一)所有可能的 Strategy。

public enum StrategyEnum {
    Strategy1,

    Strategy2,

    Strategy3
    
    // ...
}

(二)Strategy 接口。

public interface Strategy {
    /**
     * 获取 Strategy 枚举
     *
     * @return Strategy 枚举
     */
    StrategyEnum getStrategyEnum();

    /**
     * 业务处理
     */
    void doSomething();
}

(三)Strategy 的其中一个实现。

@Component
public class Strategy1 implements Strategy {

    @Override
    public StrategyEnum getStrategyEnum() {
        return StrategyEnum.Strategy1;
    }

    @Override
    public void doSomething() {
        // ...
    }
}

(四)Strategy 工厂。

@Component
public class StrategyFactory {
    private final Map<StrategyEnum, Strategy> strategyMap;

    @Autowired
    public StrategyFactory(Collection<Strategy> strategies) {
        Assert.notNull(strategies, "strategies must not be null");
        this.strategyMap = new LinkedHashMap<>(strategies.size());
        strategies.forEach((strategy) -> strategyMap.put(strategy.getStrategyEnum(), strategy));
    }

    public Strategy getStrategy(StrategyEnum strategyEnum) {
        return strategyMap.get(strategyEnum);
    }
}

(五)重写 SomeService。

@Service
public class SomeService {
    @Resource
    private StrategyFactory strategyFactory;

    public void doSomething(Some some) {
        Strategy strategy = strategyFactory.getStrategy(some.getStrategyEnum());
        if (Objects.nonNull(strategy)) {
            strategy.doSomething();
        }
    }
}

注意:获取策略的方法(getStrategy)在最坏情况下的运行时间的增长数量级是 1。

本文首发于 https://h2cone.github.io/

参考资料