代码重构思路(下单流程)
本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net
举个栗子
我任职于一家小电商,我这边的下单接口有如下的业务流程:
光看这流程图,就可以感知复杂。我在重构时就利用了设计模式,加强了它的扩展性、可读性。
模版方法
例如优惠券校验这个一小部分,伪代码如下:
if 是否使用优惠券 then
优惠券校验
else if
//do nothing
endif
我第一个想到的就是模版方法。
将不变的部分放在父类,由不同的子类去实现变化的部分。
这里变的部分是校验逻辑,不变的是 if-else。
父类 AbstractOrderSubmitTransactionTemplate 如下:
public abstract class AbstractOrderSubmitTransactionTemplate implements OrderSubmitTransactionTemplate{
protected abstract boolean test(Context context);
protected abstract void doValidate(Context context);
protected abstract void doPrepare(Context context);
protected abstract void doCommit(Context context);
protected abstract void doRollback(Context context);
@Override
public void validate(Context context) {
if(test(context)){
//日志记录
doValidate(context);
//日志记录
}
}
@Override
public final void prepare(Context context) {
if(test(context)){
//日志记录
doPrepare(context);
//日志记录
}
}
@Override
public final void commit(Context context) {
if(test(context)){
//日志记录
doCommit(context);
//日志记录
}
}
@Override
public final void rollback(Context context) {
if(test(context)){
//日志记录
doRollback(context);
//日志记录
}
}
}
根据接口分离原则,定义供调用者调用的接口 OrderSubmitTransactionTemplate
public interface OrderSubmitTransactionTemplate{
void validate(Context context);
void prepare(Context context);
void commit(Context context);
void rollback(Context context);
}
举例,用于处理优惠券的子类 CouponTransactionTemplate 实现:
public class CouponTransactionTemplate extends AbstractOrderSubmitTransactionTemplate{
@Override
public boolean test(Context context) {
return context.isUseCoupon();
}
@Override
protected void doValidate(Context context) {
//优惠券校验
}
@Override
protected void doPrepare(Context context) {
//优惠券预扣减
}
@Override
protected void doCommit(Context context) {
//优惠券扣减提交
}
@Override
protected void doRollback(Context context) {
//优惠券扣减回滚
}
}
基于模版方法的改造就完成了,我的业务代码也就变成了下面这样:
public void submit(Context context){
//校验
stockTransactionTemplate.validate(context);
couponTransactionTemplate.validate(context);
activityTransactionTemplate.validate(context);
//预扣减
stockTransactionTemplate.prepare(context);
couponTransactionTemplate.prepare(context);
activityTransactionTemplate.prepare(context);
try{
//下单流程
//确认扣减
stockTransactionTemplate.commit(context);
couponTransactionTemplate.commit(context);
activityTransactionTemplate.commit(context);
}catch (Exception e){
//回滚扣减
stockTransactionTemplate.rollback(context);
couponTransactionTemplate.rollback(context);
activityTransactionTemplate.rollback(context);
}
}
责任链模式
光使用模版方法改造,其实还不够,只是减少了 if-else,并没有符合开闭原则,也就是说当我需要加入一种全新的优惠券时,又需要改动我下单方法(submit)
所以,我又想到了责任链模式。
调用者将请求传给责任链,具体哪些个对象负责执行,由责任链上的对象自行判断是否处理。
我在 AbstractOrderSubmitTransactionTemplate 类中定义的 test 方法,其实已经可以实现对象自行判断是否处理。所以这里我只要把所有的 AbstractOrderSubmitTransactionTemplate 子类挨个轮询就可以实现责任链模式。
例如校验方法(validate)可以这样实现:
@Autowired
private List<AbstractOrderSubmitTransactionTemplate> transactionTemplates;
public void validate(Context context){
for (AbstractOrderSubmitTransactionTemplate transactionTemplate : transactionTemplates) {
transactionTemplate.validate(context);
}
}
这里可以利用 spring 的 @Autowired 注解,将 AbstractOrderSubmitTransactionTemplate 的子类注入到一个 list 中。
经过改造后,我的业务代码成了这样:
public void submit(Context context){
//校验
validate(context);
//预扣减
prepare(context);
try{
//下单流程
//确认扣减
commit(context);
}catch (Exception e){
//回滚扣减
rollback(context);
}
}
策略模式
假设某一天,随着公司业务扩张,需要根据不同的订单类型,执行不同的下单逻辑。也就是我的下单流程要改,有些订单类型插入 A 库,有些订单类型插入 B 库,有些订单类型可能不插库直接放到其他系统去。
伪代码如下
//校验
//预扣减
//下单流程
if 订单类型为1 then
插入A库
else if 订单类型为2 then
插入B库
else if 订单类型为3 then
调用C系统同步订单
end if
//扣减提交/扣减回滚
校验、预扣减、扣减提交 / 扣减回滚这块和下单流程这块是一对多的关系,所以我这边想到的是利用策略模式去实现这个需求。
业务模块之间是 1 对多的关系,将每个模块封装成策略,组合使用。
下单流程策略接口 OrderSubmitStrategy:
public interface OrderSubmitStrategy{
void submit(Context context);
}
依旧使用模版方法定义了抽象的父类
public abstract class AbstractOrderSubmitStrategy implements OrderSubmitStrategy {
protected abstract void doSubmit(Context context);
public final void submit(Context context){
//日志记录
this.doSubmit(context);
//日志记录
}
}
其中一个子类如下:
public class OrderSubmitV1Strategy extends AbstractOrderSubmitStrategy{
@Override
protected void doSubmit(Context context) {
//v1 下单
}
}
经过改造,我的业务代码变成了这样:
public void submit(Context context,OrderSubmitStrategy orderSubmitStrategy){
//校验
validate(context);
//预扣减
prepare(context);
try{
//下单流程
orderSubmitStrategy.submit(context);
//确认扣减
commit(context);
}catch (Exception e){
//回滚扣减
rollback(context);
}
}
在具体调用时,根据不同策略执行不同的下单逻辑:
@PostMapping("/v1/submit")
public SubmitRes submit(SubmitForm form){
Context context = new Context(form);
orderSubmitBean.submit(context,orderSubmitV1Strategy);
return context.getSubmitRes();
}
@PostMapping("/v2/submit")
public SubmitRes submit(SubmitForm form){
Context context = new Context(form);
orderSubmitBean.submit(context,orderSubmitV2Strategy);
return context.getSubmitRes();
}
观察者模式
下单之后,需要通知其他系统,比如大数据系统。这些通知并不需要保证强一致性,只需要最终一致性,一般我们都利用消息中间件来通知,但是通知多了,发送消息代码的严重影响代码简洁,而且每次有新的消息需要发送都需要改动 submit 方法。
于是,我就想到的观察者模式来实现。
当一个对象被修改时,则会自动通知依赖它的对象
观察者接口:
public static interface OrderSubmitListener{
void handle(Context context);
}
notifyListeners 方法:
@Autowired
private List<OrderSubmitListener> listeners;
void notifyListeners(Context context){
for (OrderSubmitListener listener : listeners) {
listener.handle(context);
}
}
这里可以利用 spring 的 @Autowired 注解,将 OrderSubmitListener 的子类注入到一个 list 中。
改造后代码如下:
public void submit(Context context,OrderSubmitStrategy orderSubmitStrategy){
//校验
validate(context);
//预扣减
prepare(context);
try{
//下单流程
orderSubmitStrategy.submit(context);
//确认扣减
commit(context);
}catch (Exception e){
//回滚扣减
rollback(context);
}
notifyListeners(context);
}