目录

  • 前言思考
  • 实现落地
  • 小结


前言思考

随着业务需求不断迭代更新,系统逻辑越来越复杂。if else堆砌让人眼花缭乱。
那么此时就可以考虑使用设计模式,重构代码逻辑

采用什么设计模式,或者哪几种设计模式组合,与实际业务场景、逻辑有关系

以下面这个场景为例:

现在要将一批货物从A地点运往B地点,涉及三方:始发方、目的方、运输媒介方,货物如果在此时发生了丢失,那么具体是哪一方的责任?
现在承担包裹问题责任方有四种:始发方、目的方、始发方和目的方、运输媒介方
每种责任方处理结果,都有不同的处理方案,例如如果是运输丢失,那么司机的人力公司需要承担这部分赔偿,如果是始发地,则这部分物品成本需要挂在始发地等等

如果用if else 去写责任方处理结果的方法,那么就要嵌套好几层,如下伪代码所示:

if(jugmentParty == "始发方"){
	//始发方处理
}else if(jugmentParty == "目的方"){
	//目的方处理
}else if(jugmentParty == "始发方和目的方"){
	//始发方和目的方处理
}else{
	//运输媒介方处理
}

责任方会随着业务场景不断增加,可能还会增加快递员的责任,这么无限套下去,这个方法中的代码会长,并且不易扩展,每次都要动这个方法的东西,很难不保证其他正常运行的逻辑不受影响,非常不符合单一职责原则和开闭原则。

并且业务侧希望可以按照明细商品纬度处理责任或者整个运单包裹纬度处理责任,这有什么区别呢,例如说一个包裹里面有9个杯子,只有一个碎了,那实际上只需要赔偿一个杯子的价格即可,整单责任。例如包裹丢失等。

这种情况下,就可以抽象一下业务场景,无论有多少责任方,要做的事情都是包裹责任处理,处理分两种,一种是单个商品纬度处理,一种是整个包裹纬度处理。然后针对不同责任方,有不同的处理细节。

这种场景我觉得十分适合使用策略模式。

实现落地

(1)首先创建一个统一接口类,定义整包裹处理和单商品处理的方法

@Component("JudgmentStrategy")
public interface JudgmentStrategy {
    /**
     * 整包裹处理责任
     * @param confirmReq
     */
    void wholeOrderOperate(BizJudgmentConfirmReq confirmReq);

    /**
     * 单个商品处理责任
     * @param confirmReq
     */
    void singleOrderOperate(BizJudgmentConfirmReq confirmReq);
}

(2)创建不同责任方的实现类,实现此接口的两个方法

始发地

/**
 * 始发地责任处理
 */
@Slf4j
@Component("Origin")
public class OriginJudgmentStrategy implements JudgmentStrategy{
    @Override
    public void wholeOrderOperate(BizJudgmentConfirmReq confirmReq) {
        //始发地整单处理责任结果
    }

    @Override
    public void singleOrderOperate(BizJudgmentConfirmReq confirmReq) {
        //始发地单个商品处理责任结果
    }
}

目的地责任处理

/**
 * 目的地责任处理
 */
@Slf4j
@Component("Dest")
public class DestJudgmentStrategy implements JudgmentStrategy{
    @Override
    public void wholeOrderOperate(BizJudgmentConfirmReq confirmReq) {
        //始发地整单处理责任结果
    }

    @Override
    public void singleOrderOperate(BizJudgmentConfirmReq confirmReq) {
        //始发地单个商品处理责任结果
    }
}

始发地&目的地双方责任处理

/**
 * 始发地&目的地双方责任处理
 */
@Slf4j
@Component("OriginAndDest")
public class OriginAndDestJudgmentStrategy implements JudgmentStrategy{
    @Override
    public void wholeOrderOperate(BizJudgmentConfirmReq confirmReq) {
        //始发地整单处理责任结果
    }

    @Override
    public void singleOrderOperate(BizJudgmentConfirmReq confirmReq) {
        //始发地单个商品处理责任结果
    }
}

运输方责任处理

/**
 * 运输方责任处理
 */
@Slf4j
@Component("Transport")
public class TransportJudgmentStrategy implements JudgmentStrategy{
    @Override
    public void wholeOrderOperate(BizJudgmentConfirmReq confirmReq) {
        //始发地整单处理责任结果
    }

    @Override
    public void singleOrderOperate(BizJudgmentConfirmReq confirmReq) {
        //始发地单个商品处理责任结果
    }
}

(3)创建一个Context类,用于关联调用方和策略方法的一个媒介,也叫上下文。也就是说我们真正去调用策略方法是不直接引用JudgmentStrategy的,而且引用Context类。
上面通过工厂的形式创建策略类的实现类,直接通过@Autowired注入到Context上下文中。

@Slf4j
@Component
public class JudgmentStrategyContext {

    private final Map<String, JudgmentStrategy> strategyMap = new ConcurrentHashMap<String, JudgmentStrategy>();

    /**
     * 注入所有实现了JudgmentStrategy接口的Bean
     *
     * @param strategyMap
     */
    @Autowired
    public void JudgmentStrategy(Map<String, JudgmentStrategy> strategyMap) {
        this.strategyMap.clear();
        strategyMap.forEach(this.strategyMap::put);
    }

    /**
     * 整单处理库存
     *
     * @param confirmReq
     */
    public void wholeOrderOperate(BizJudgmentConfirmReq confirmReq) {
        log.info("JudgmentStrategyContext#wholeOrderOperate--入参confirmReq:{}", JSONObject.toJSONString(confirmReq));      	
        strategyMap.get(getBean(confirmReq)).wholeOrderOperate(confirmReq);
    }

     /**
     * 明细纬度处理库存
     *
     * @param confirmReq
     */
    public void singleOrderOperate(BizJudgmentConfirmReq confirmReq) {
        log.info("JudgmentStrategyContext#singleOrderOperate--入参confirmReq:{}", JSONObject.toJSONString(confirmReq));
        strategyMap.get(getBean(confirmReq)).singleOrderOperate(confirmReq);
    }

    private String getBean(BizJudgmentConfirmReq confirmReq) {
	    if(confirmReq == null || confirmReq.getJudgmentParty() == null){
	    	throw new BusinessException("责任处理失败,责任方入参不能空");
	    }
        HandoverJudgmentPartyEnum partyEnum = HandoverJudgmentPartyEnum.getEnumByValue(confirmReq.getJudgmentParty());
        if (partyEnum == null) {
            throw new BusinessException("责任处理失败,未匹配到处理责任方");
        }
        switch (partyEnum) {
        	case TRANSPORT:
                return "Transport";
            case ORIGIN:
                return "Origin";
            case DEST:
                return "Dest";
            case ORIGIN_AND_DEST:
                return "OriginAndDest";
            default:
                throw new BusinessException("责任处理失败,未匹配到处理责任方");
        }
    }
}

补充枚举类以及入参类

/**
 * 责任方枚举
 */
public enum HandoverJudgmentPartyEnum {
    TRANSPORT(1, "运输媒介方"),
    ORIGIN(2, "始发方"),
    DEST(3, "目的方"),
    ORIGIN_AND_DEST(4, "始发方&目的方"),
    ;

    private Integer value;
    private String name;

    HandoverJudgmentPartyEnum(Integer value, String name) {
        this.value = value;
        this.name = name;
    }

    public Integer getValue() {
        return value;
    }

    public String getName() {
        return name;
    }

    public static String getNameByValue(Integer value){
        for(HandoverJudgmentPartyEnum sourceEnum: HandoverJudgmentPartyEnum.values()){
            if(sourceEnum.getValue().equals(value)){
                return sourceEnum.getName();
            }
        }
        return null;
    }

    public static HandoverJudgmentPartyEnum getEnumByValue(Integer value) {
        if (null == value) {
            return null;
        }
        for (HandoverJudgmentPartyEnum item : HandoverJudgmentPartyEnum.values()) {
            if (Objects.equals(value,item.value)) {
                return item;
            }
        }
        return null;
    }
}
/**
 * 责任方确认入参
 */
@Data
public class BizJudgmentConfirmReq {
    /**
     * 单号编码
     */
    private String businessCode;

    /**
     * 责任方 {@linkplain com.jd.hr.domain.enums.HandoverJudgmentPartyEnum}
     */
    private Integer judgmentParty;

    /**
     * 责任依据
     */
    private String judgmentBasis;

    /**
     * 操作人erp
     */
    private String operateUser;

    /**
     * 操作人姓名
     */
    private String operateName;
}

(4)调用策略方法

@Slf4j
@Service
public class JugmentOrderServiceImpl extends IJugmentOrderService{
	@Resource
    private JudgmentStrategyContext judgmentStrategyContext;
    
	@Override
    public void wholeOrderOperateTest(BizJudgmentConfirmReq confirmReq) {
        judgmentStrategyContext.wholeOrderOperate(confirmReq);
    }
    @Override
    public void singleOrderOperateTest(BizJudgmentConfirmReq confirmReq) {
        judgmentStrategyContext.singleOrderOperate(confirmReq);
    }
}

小结

通过上面的代码实现,可以看出接口类只负责业务策略的定义,定义各个方面策略的标准;策略的具体实现,可以认为是多个方面的策略,或者是多个角色的策略就有不同的实现。

Context上下文类负责业务逻辑的编排,封装了策略的执行细节,具体的实现服务只需要调用Context类的方法,而不需要了解具体策略对象的实现细节。如何编排策略完全在Context封装好。

策略模式的优势:
通过策略模式(或变种)的应用,实现了面向接口而非实现编程,满足了职责单一、开闭原则,从而达到了功能上的高内聚低耦合、提高了可维护性、扩展性以及代码的可读性。

设计模式是为了帮助我们从复杂的业务场景中解脱出来,提升代码的可读性,可维护性。但是在实际应用过程中也不必拘泥于设计模式本身,也可以结合所使用的框架进行变种处理。