曾梦想 if-else 走天涯?看看“责任树模式”优化
如何解决接口升级,在保证兼容老版本的情况下轻松开发新版本业务逻辑?
如何根据入参 p1、p2、p3 等的不同组合进行策略定位?
可以看到,两种设计模式都不完全符合目前这个场景:责任链模式可以实现逐级委托,但每一级又不能像策略模式那样路由到不同的处理者上;策略模式通常只有一层路由,不易实现多个参数的策略组合。
代码结构更清晰,可维护性提升:没有了各种卫语句的跳转 & 维护性巨差的巨型方法,函数可以收敛在理想的 50 行内;
调试阶段问题易定位:同样由于策略间隔离,调试时可以直接定位到指定策略的业务逻辑代码,不需要逐句排查;
后续迭代人力成本降低;
新增需求修改代码不易出错:策略间隔离,不需要完整看一遍大函数理清逻辑再修改,只需要无脑添加一条路由 + 新的策略实现方法即可;
Router 是一个抽象类,负责定义如何路由到下游的多个子节点;
Handler 是接口,负责实现每个节点的业务逻辑。
除了根节点(入口)外,每个节点都实现了 Handler 接口。根节点只继承 Router 抽象类;
所有叶子节点只实现 Handler 接口而无需继承 Router 抽象类(无需再向下委托);
除了根节点和叶子节点外的其他节点,都是上一层的 Handler,同时是下一层的 Router;
@Component
public abstract class AbstractStrategyRouter<T, R> {
public interface StrategyMapper<T, R> {
StrategyHandler<T, R> get(T param);
}
private StrategyMapper<T, R> strategyMapper;
@PostConstruct
private void abstractInit() {
strategyMapper = registerStrategyMapper();
Objects.requireNonNull(strategyMapper, "strategyMapper cannot be null");
}
@Getter
@Setter
@SuppressWarnings("unchecked")
private StrategyHandler<T, R> defaultStrategyHandler = StrategyHandler.DEFAULT;
public R applyStrategy(T param) {
final StrategyHandler<T, R> strategyHandler = strategyMapper.get(param);
if (strategyHandler != null) {
return strategyHandler.apply(param);
}
return defaultStrategyHandler.apply(param);
}
protected abstract StrategyMapper<T, R> registerStrategyMapper();
}
publicR applyStrategy(T param)
方法即可获取该节点的期望输出。框架会自动根据定义的路由逻辑将 param 传递到对应的子节点,再由子节点不断向下分发直到叶子节点或可以给出业务输出的一层。这个过程有点类似递归或者分治的思想。/**
* @author: 寻弈
* @date: 2020/4/16 2:46 下午
*/
public interface StrategyHandler<T, R> {
@SuppressWarnings("rawtypes")
StrategyHandler DEFAULT = t -> null;
/**
* apply strategy
*
* @param param
* @return
*/
R apply(T param);
}
R apply(T param);
方法中首先进行一定异常入参拦截,遵循 fail-fast 原则,避免将这一层可以拦截的错误传递到下一层,同时也要避免「越权」做非本层职责的拦截校验,避免产生耦合,为后面业务拓展挖坑。在拦截逻辑后直接调用本身 Router 的 publicR applyStrategy(T param)
方法路由给下游节点即可。任何「结论」都有其所处背景、上下文细节等,通过一句话指导工作是不成立的。优秀的架构师可以给出架构设计是在理论基础、大量实践、不断思考总结以及无数采坑的经验的基础上得来的,而不是他知道一句别人都不知道的「咒语」;
Knuth 这句话更偏重于反对奇技淫巧、细枝末节的性能优化,因为在「过早」的时候无法准确获知系统的瓶颈且局部的优化不仅不能带来收益,反而会造成更大的代价。他批评的恰恰是不着眼于整体架构的局部视角对系统的破坏,而架构设计正是需要从整体视角去做选择与权衡。因此将 Knuth 这句话直接推广到「架构设计」上并不妥当;
很多人觉得在项目开发时需求经常「瞬息万变」、「朝令夕改」,而做优化又需要花费大量时间思考,根本没有精力优化。我认为这种论述也是不成立的,凭什么认为等到坏味道严重、历史包袱沉重的时候就有精力、能力和胆量做优化了呢?
何时是所谓的「不早」很难界定,其实我们永远都无法确定自己掌握了足够的细节可以进行绝对正确的优化。在现实世界中,受到时间维度的限制,我们永远无法达成全局最优,只能以局部最优不断去逼近全局最优。我觉得等到坏味道严重不得不重构的时候才想起优化已为时过晚;
这句话不应该成为不做设计的借口,即使最终提交的代码仍是「if-else」版本,也不应省略思考、推演、权衡的过程,日常需求是练兵场,是精进技术的必经之路;