
-
如何解决接口升级,在保证兼容老版本的情况下轻松开发新版本业务逻辑?
-
如何根据入参 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();
}
如果子节点路由逻辑比较简单,可以直接通过 if-else 进行分发。当然如果为了更好地性能、适应更复杂的分发逻辑也可以使用 Map 等保存映射。
对于实现了该抽象类的 Router 节点,只需要调用其 publicR applyStrategy(T param)
方法即可获取该节点的期望输出。框架会自动根据定义的路由逻辑将 param 传递到对应的子节点,再由子节点不断向下分发直到叶子节点或可以给出业务输出的一层。这个过程有点类似递归或者分治的思想。
StrategyHandler接口
/**
* @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);
}
对于其他责任树中的中间层节点,都需要同时继承 Router 抽象类和实现 Handler 接口,在 R apply(T param);
方法中首先进行一定异常入参拦截,遵循 fail-fast 原则,避免将这一层可以拦截的错误传递到下一层,同时也要避免「越权」做非本层职责的拦截校验,避免产生耦合,为后面业务拓展挖坑。在拦截逻辑后直接调用本身 Router 的 publicR applyStrategy(T param)
方法路由给下游节点即可。
更多思考
至此,关于如何通过「责任树模式」优化这个需求场景的介绍就基本结束了,这不是一个复杂的需求,更不是一个多么精妙的优化,这只是日常需求开发中通过设计模式优化代码的一个小例子。
最后再简单聊聊我在日常需求开发过程中关于架构设计部分的一些思考。
其实并不是说用「if-else」很 Low,用设计模式就 Niubility,二者各有其擅长的应用场景,在合适的场景使用合适的代码才是正道。其实「if-else」足以满足大部分日常需求的开发,且简单、灵活、可靠。这里的「if-else」泛指朴素直白的编程模式,仅以实现需求业务功能为目的的编码方式。当然,有些同学不满足于此,希望可以通过经过思考的、更优的架构设计使代码变的更简洁、拓展性更好、性能更优、可读性更好等等。不过对于此也存在反对的论述,谓之「过早优化乃万恶之源」。
这句话源自 Donald Knuth 老人家:

-
任何「结论」都有其所处背景、上下文细节等,通过一句话指导工作是不成立的。优秀的架构师可以给出架构设计是在理论基础、大量实践、不断思考总结以及无数采坑的经验的基础上得来的,而不是他知道一句别人都不知道的「咒语」;
-
Knuth 这句话更偏重于反对奇技淫巧、细枝末节的性能优化,因为在「过早」的时候无法准确获知系统的瓶颈且局部的优化不仅不能带来收益,反而会造成更大的代价。他批评的恰恰是不着眼于整体架构的局部视角对系统的破坏,而架构设计正是需要从整体视角去做选择与权衡。因此将 Knuth 这句话直接推广到「架构设计」上并不妥当;
-
很多人觉得在项目开发时需求经常「瞬息万变」、「朝令夕改」,而做优化又需要花费大量时间思考,根本没有精力优化。我认为这种论述也是不成立的,凭什么认为等到坏味道严重、历史包袱沉重的时候就有精力、能力和胆量做优化了呢?
-
何时是所谓的「不早」很难界定,其实我们永远都无法确定自己掌握了足够的细节可以进行绝对正确的优化。在现实世界中,受到时间维度的限制,我们永远无法达成全局最优,只能以局部最优不断去逼近全局最优。我觉得等到坏味道严重不得不重构的时候才想起优化已为时过晚;
-
这句话不应该成为不做设计的借口,即使最终提交的代码仍是「if-else」版本,也不应省略思考、推演、权衡的过程,日常需求是练兵场,是精进技术的必经之路;