原创|关于一次产品需求程序设计及优化的经历

文章目录

  • 一、流程梳理
  • 二、设计梳理
  • 三、技术方案
    • 3.1、下单接口扩展
      • 3.3.1、Request类新增deviceType
      • 3.3.2、申请单新增字段产品策略(productStrategy)
      • 3.3.3、下单产品策略的处理逻辑
    • 3.2、询价模块的设计
      • 3.2.1、Context设计
      • 3.2.2、ProductStrategy类设计
        • 3.2.2.1、AbstractProductStrategy类
        • 3.2.2.2、ProxyLiveStandardPriceStrategy
        • 3.2.2.3、ProxyLiveStandardQuantityStrategy
        • 3.2.2.4、HandleDispatcher
    • 3.3、不同场景下的价格计算策略设计
      • 3.3.1、UML类图
        • 3.3.1.1、Calculator UML类图
        • 3.3.1.2、Context UML类图
      • 3.3.2、Calculator
        • 3.3.2.1、AbstractCalculator
        • 3.3.2.2、DeductAmountCalculator
      • 3.3.3、Context
        • 3.3.3.1、AbstractCalculateContext
        • 3.3.3.2、PartialDeductAmountCalculateContext
      • 3.3.4、CalculateMediator
    • 3.4、付款与退款的设计
      • 3.4.1、Context类的设计
      • 3.4.2、TransactionHandler类的设计
      • 3.4.3、PaymentTransactionHandler类的设计
        • 3.4.3.1、StandardPricePaymentTransactionHandler类的设计
        • 3.4.3.2、StandardPricePaymentTransactionHandler类的设计
      • 3.4.4、TransactionHandler类的设计
        • 3.4.4.1、StandardPriceRefundTransactionHandler类的设计
        • 3.4.4.2、StandardQuantityRefundTransactionHandler类的设计
  • 四、优化设计
    • 1、计算模型优化
    • 2、Dispatcher优化
      • 2.1、第一版Dispatcher设计
      • 2.2、第二版Dispatcher设计
        • 2.2.1、Context接口
        • 2.2.2、Handle接口
        • 2.2.3、AbstractDispatcher抽象类
  • 五、总结

由于一次需求变更,由于的业务流程不支持这种新的场景,这也意味着需要设计支持新场景的业务逻辑处理。但是又要考虑后期的扩展性,同时尽可能不影响原来的业务,需要分析利弊寻找一种技术设计方案来解决当前需要解决的问题。通过分析,勾勒出涉及的变动点,这些变动点如果在原有代码新增if else就会造成程序的可维护性很差,而且不利于后期场景复用,因此寻找一种策略+分发的处理机制,通过上下文封装场景,然后通过调度器来分发处理器完成处理,同时把稳定的能力下沉到能力层,跟业务场景有关的通过路由分发完成最终业务逻辑处理。

一、流程梳理

原创|关于一次产品需求程序设计及优化的经历_第1张图片

通过梳理上图,整个业务流程可以精简如上。通过分析上图可以得出如下结论:

  • 该业务流程需要对接外部资产支付系统,意味着资金交易的生命周期维护(即上述红色流程)。
  • 紫色的块意味着业务逻辑需要变更,原有的策略假设为A,新增一种策略为B,也有意味着相关变动点需要考虑场景选择A或者B,以便处理。
  • 涉及金额处理的,比如付款金额或者退款金额,需要根据场景来选择具体如何计算,场景不同,计算方式也不同,假设两种不同计算金额方式为A、B两种策略。
  • 在交付环节,业务的特殊之处就是在下单的时候,产品类型分为基础班(交付1份)、高级版(交付3份),所以交付完成的处理触发机制不同。与此同时,如果交付数量未达标,还涉及退款处理。
  • 两种场景的不同之处,如果商品总额可以除尽意味着可以按照商品数量,交由资产系统完成付款和退款的处理。但是不能除尽意味着需要业务方自己计算付款和退款金额。
  • 业务订单有自己的状态机(未交付、已交付),交易订单也有自己的状态机(冻结、支付中、支付成功、结算完成)。这两种领域之间有联系,业务订单先于交易订单创建,业务订单触发交付的时候,资金订单才发起付款处理。
  • 业务订单由于不同的产品类型,意味着交付是分阶段付款。这也意味着商品总额如果除以商品数量不尽的情况,需要特殊处理。比如一个商品总额10¥,购买3个商品,通过与产品沟通,交付第一个付款3¥,交付第二个付款3¥,最后一个交付付款4¥,然后结算完成。可以这么处理。

二、设计梳理

不同业务线的产品类型划分,也意味着购买不同产品类型的产品触发的交付条件不同,进而触发付款的条件也不同,付款金额和退款金额也有所区别。
原创|关于一次产品需求程序设计及优化的经历_第2张图片

原创|关于一次产品需求程序设计及优化的经历_第3张图片
假设一订单购买的是高级版的商品,此商品总额10¥,需要交付三份,在指定时间未达成交付,意味着未交付的那几份,需要退款。这里与产品沟通,可以采取四舍五入的金额处理方式。Math.floor(10/3) = 3,最后一份则 buyAmount-paidAmount即 10-6。

原创|关于一次产品需求程序设计及优化的经历_第4张图片
未全交付场景下的自动结算流程,先退款未交付的,再付款已交付的。
原创|关于一次产品需求程序设计及优化的经历_第5张图片
原有的商品定价,对于资产系统而言

原创|关于一次产品需求程序设计及优化的经历_第6张图片

三、技术方案

3.1、下单接口扩展

3.3.1、Request类新增deviceType

    /**
     * 1-iOS、2-android、3-其他
     */
    @ThriftField(5)
    private Integer deviceType;

3.3.2、申请单新增字段产品策略(productStrategy)

该字段是后续付款和退款时,具体选择商品数量(quantity)和商品价格(price)两种策略模型。

新增一个产品策略枚举:

/**
 * 产品策略类型
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/7 8:21 PM
 */
@Getter
public enum ProductStrategyEnum {
    /**
     * 标准产品策略
     */
    UNKNOWN(0, "UnknownProductStrategy", "Unknown"),

    /**
     * 标准产品商品数量确认付款策略
     */
    STANDARD_QUANTITY(1, "StandardQuantityProductStrategy", "quantity"),

    /**
     * 标准产品商品价格确认付款策略
     */
    STANDARD_PRICE(2, "StandardPriceProductStrategy", "price"),

    ;

    /**
     * 策略索引
     */
    private final int value;

    /**
     * 策略名称
     */
    private final String name;

    /**
     * 策略简称
     */
    private final String shortName;

    ProductStrategyEnum(int value, String name, String shortName) {
        this.value = value;
        this.name = name;
        this.shortName = shortName;
    }

    public static ProductStrategyEnum valueOfShortName(String shortName) {
        for (ProductStrategyEnum each : values()) {
            if (Objects.equals(each.getShortName(), shortName)) {
                return each;
            }
        }
        return UNKNOWN;
    }
}

下单BO类和POJO类新增属性:

    /**
     * 产品策略
     * {@link ProductStrategyEnum#getShortName()}
     */
    private String productStrategy;

3.3.3、下单产品策略的处理逻辑

定义一个类:ProxyLiveComponent,新增如下方法:getProductStrategy

    /**
     * 获取产品策略
     *
     * @param requestBO 请求
     * @return 返回
     */
    public ProductStrategyEnum getProductStrategy(JobApplyLiveRequestBO requestBO) {
        if (SideBusinessSubtypeEnum.isSocial(requestBO.getSideBusinessSubtype()) && Objects.equals(requestBO.getDeviceType(), 1)) {
            int deliveryCount = getSocialProxyDeliveryCount(requestBO.getProductType());
            // 基础版、高级版=职位数*投递量 ;至尊版=职位数
            int quantity = requestBO.getJobNumbers().size() * deliveryCount;
            GetOrderPriceRequestBO getOrderPriceRequest = assetPaymentConverter.convertToGetOrderPriceRequestBO(requestBO, quantity);
            //下单时需要向资产系统询价,如果账号资金充足则可以选择商品数量;不足只能选择商品价格。
            PaymentTypeEnum paymentType;
            try {
                paymentType = assetPaymentBusiness.autoPrice(getOrderPriceRequest);
            } catch (Exception e) {
                log.error("[getProductStrategy]下单获取产品策略异常,req={}", JsonUtils.toJson(requestBO), e);
                throw new BusinessException("获取产品策略异常", e);
            }
            if (PaymentTypeEnum.RMB == paymentType) {
                return ProductStrategyEnum.STANDARD_PRICE;
            }
        }
        return ProductStrategyEnum.STANDARD_QUANTITY;
    }

然后再business层,新增如下代码

ProductStrategyEnum productStrategy = proxyLiveComponent.getProductStrategy(requestBO);
jobApplyLiveBO.setProductStrategy(productStrategy.getShortName());
//入库

3.2、询价模块的设计

原创|关于一次产品需求程序设计及优化的经历_第7张图片

3.2.1、Context设计

上下文类的作用在于封装整个业务处理所依赖的相关参数,后续扩展只需要在此类增加成员属性即可。

/**
 * 上下文接口
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 7:14 AM
 */
public interface Context {
}

具体场景Context实现Context接口。

/**
 * 产品策略上下文对象
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/7 8:55 PM
 */
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@Data
public class ProductEnquiryContext implements Context {

    /**
     * 产品策略
     */
    private ProductStrategyEnum productStrategy;

    /**
     * 上下文依赖的请求参数
     */
    private Request request;

    /**
     * 上下文处理结果
     */
    private Response response;

    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    @Data
    public static class Request {

        /**
         * 请求参数
         */
        private QualifyProductRequestBO qualifyProductRequest;

        /**
         * 产品
         */
        private LiveProductBO liveProduct;

        /**
         * 申请单
         */
        private JobApplyLiveBO jobApplyLive;
    }

    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    @Data
    public static class Response {

        /**
         * 数量
         */
        private Integer quantity;

        /**
         * 询价属性
         */
        private Map<String, String> priceAttributes;
    }
}

3.2.2、ProductStrategy类设计

/**
 * 处理接口
 * 适用于多场景、多策略的处理器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 6:46 AM
 */
public interface Handle<C extends Context> {
    /**
     * 获取处理器名称
     *
     * @return 处理器名称
     */
    String getName();

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    void execute(C context);
}

3.2.2.1、AbstractProductStrategy类

/**
 * 抽象产品策略
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/8 2:28 PM
 */
@Slf4j
public abstract class AbstractProductStrategy implements Handle<ProductEnquiryContext> {

    final static String LOG_PREFIX = "[ProductStrategy]";

    /**
     * 产品策略
     */
    protected ProductStrategyEnum productStrategy;

    @Autowired
    protected ProxyLiveComponent proxyLiveComponent;

    public AbstractProductStrategy(ProductStrategyEnum productStrategy) {
        this.productStrategy = productStrategy;
    }

    /**
     * 执行策略
     *
     * @param context 上下文对象
     */
    @Override
    public void execute(ProductEnquiryContext context) {
        ProductEnquiryContext.Request request = context.getRequest();
        JobApplyLiveBO jobApplyLive = request.getJobApplyLive();
        ProductEnquiryContext.Response response = ProductEnquiryContext
            .Response.builder().quantity(calculateQuantity(context))
            .priceAttributes(getPriceAttributes(context))
            .build();
        log.info("{}|{}|applyId={},response={}", LOG_PREFIX, getName(), jobApplyLive.getId(), JsonUtils.toJson(response));
        context.setResponse(response);
    }

    /**
     * 计算商品数量
     *
     * @param context 上下文对象
     * @return 返回值
     */
    protected abstract Integer calculateQuantity(ProductEnquiryContext context);

    /**
     * 获取询价属性
     *
     * @param context 上下文对象
     * @return 返回值
     */
    protected abstract Map<String, String> getPriceAttributes(ProductEnquiryContext context);

    public ProductStrategyEnum getProductStrategy() {
        return productStrategy;
    }
}

3.2.2.2、ProxyLiveStandardPriceStrategy

商品数量询价策略类

Service
@Slf4j
public class ProxyLiveStandardPriceStrategy extends AbstractProductStrategy {

    final int THRESHOLD = 5;

    public ProxyLiveStandardPriceStrategy() {
        super(ProductStrategyEnum.STANDARD_PRICE);
    }

    @Override
    public String getName() {
        return ProxyLiveStandardPriceStrategy.class.getSimpleName();
    }

    @Override
    protected Integer calculateQuantity(ProductEnquiryContext context) {
        int quantity = 1;
        ProductEnquiryContext.Request request = context.getRequest();
        JobApplyLiveBO jobApplyLive = request.getJobApplyLive();
        List<String> jobNumbers = JSONObject.parseArray(jobApplyLive.getJobInfo(), String.class);
        if (jobNumbers.size() > THRESHOLD) {
            throw new BusinessException("询价失败!");
        }
        return quantity;
    }

    @Override
    protected Map<String, String> getPriceAttributes(ProductEnquiryContext context) {
        ProductEnquiryContext.Request request = context.getRequest();
        JobApplyLiveBO jobApplyLive = request.getJobApplyLive();
        List<String> jobNumbers = JSONObject.parseArray(jobApplyLive.getJobInfo(), String.class);
        Map<String, String> attributes = Maps.newHashMap();
        ProductTypeEnum productType = ProductTypeEnum.valueOf(jobApplyLive.getProductType());
        ProductSKUEnum productSKU = ProductSKUEnum.valueOf(jobNumbers.size(), productType);
        Optional.ofNullable(productSKU).orElseThrow(() -> {
            log.error("{}未查询到产品规格|jobApplyLive={}",  LOG_PREFIX, JsonUtils.toJson(jobApplyLive));
            return new NotExistException("未查询到产品规格");
        });
        attributes.put(MetaToBusinessEnum.PROXY_LIVE.getKey(), String.valueOf(productSKU.getSpecificationIndex()));
        //此字段用于资产支持小额支付
        if (jobNumbers.size() <= THRESHOLD) {
            attributes.put("hideRmb", "true");
        }
        return attributes;
    }
}

3.2.2.3、ProxyLiveStandardQuantityStrategy

商品价格询价策略类

@Service
public class ProxyLiveStandardQuantityStrategy extends AbstractProductStrategy {

    public ProxyLiveStandardQuantityStrategy() {
        super(ProductStrategyEnum.STANDARD_QUANTITY);
    }

    @Override
    public String getName() {
        return ProxyLiveStandardQuantityStrategy.class.getSimpleName();
    }

    @Override
    protected Integer calculateQuantity(ProductEnquiryContext context) {
        ProductEnquiryContext.Request request = context.getRequest();
        JobApplyLiveBO jobApplyLive = request.getJobApplyLive();
        List<String> jobNumbers = JSONObject.parseArray(jobApplyLive.getJobInfo(), String.class);
        int buyCount = proxyLiveComponent.getSocialProxyDeliveryCount(jobApplyLive.getProductType());
        int quantity = jobNumbers.size();
        if (SideBusinessSubtypeEnum.isSocial(jobApplyLive.getSideBusinessSubtype())) {
            quantity = jobNumbers.size() * buyCount;
        }
        return quantity;
    }

    @Override
    protected Map<String, String> getPriceAttributes(ProductEnquiryContext context) {
        Map<String, String> attributes = Maps.newHashMap();
        LiveProductBO liveProduct = context.getRequest().getLiveProduct();
        attributes.put(MetaToBusinessEnum.PROXY_LIVE.getKey(), String.valueOf(liveProduct.getProductType()));
        return attributes;
    }
}

3.2.2.4、HandleDispatcher

该类作用通过场景分发选择何种策略处理,对于业务方不需要具体关注内部策略的实现,只需要通过该类完成处理。

@Slf4j
public abstract class AbstractHandleDispatcher<H extends Handle, C extends Context> {
    /**
     * 获取分发器名称
     *
     * @return 分发器名称
     */
    protected abstract String getName();

    /**
     * 获取处理器集合
     *
     * @return 处理器集合
     */
    protected abstract List<H> getHandlers();

    /**
     * 查找处理器
     *
     * @param context 处理器上下文
     * @return 要分发的处理器
     */
    protected abstract Optional<H> find(C context);

    /**
     * 执行分发
     *
     * @param context 处理器上下文
     */
    public void execute(C context) {
        Optional<H> optional = find(context);
        if (optional.isPresent()) {
            optional.get().execute(context);
        } else {
            log.error("no find handler", JsonUtils.toJson(context));
        }
    }
}

ProductEnquiryDispatcher 具体询价调度器。

@Component
@Slf4j
public class ProductEnquiryDispatcher extends AbstractHandleDispatcher<AbstractProductStrategy, ProductEnquiryContext> {

    @Autowired
    private List<AbstractProductStrategy> strategies;

    @Override
    protected String getName() {
        return ProductEnquiryDispatcher.class.getSimpleName();
    }

    @Override
    protected List<AbstractProductStrategy> getHandlers() {
        return strategies;
    }

    @Override
    protected Optional<AbstractProductStrategy> find(ProductEnquiryContext context) {
        return getHandlers().stream().filter(each -> each.getProductStrategy() == context.getProductStrategy()).findAny();
    }
}

3.3、不同场景下的价格计算策略设计

原创|关于一次产品需求程序设计及优化的经历_第8张图片
业务逻辑处理时,只需要委托给JobApplyLiveCalculateMediator类来完成计算,外部不需要关注具体的计算器,内部计算器业务逻辑扩展修改,不需要业务方修改,做到职责隔离。

3.3.1、UML类图

3.3.1.1、Calculator UML类图

原创|关于一次产品需求程序设计及优化的经历_第9张图片
每一种场景都是一个计算器,如果对场景进行计算调整,只需要找到对应的计算器修改逻辑即可。倘若后续增加一种场景,只需要继承基类即可。

3.3.1.2、Context UML类图

原创|关于一次产品需求程序设计及优化的经历_第10张图片
每一种计算器对应一个Context上下文类,该类封装了计算所需的相关依赖参数。

3.3.2、Calculator

3.3.2.1、AbstractCalculator

该类是一个泛型类,这样便于具体场景扩展,因为计算结果有的需要返回Integer,有的返回BigDecimal,有的是具体一个BO类,所以方便扩展,灵活性强,符合代码松耦合的设计目标。

/**
 * 抽象计算器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/17 9:59 AM
 */
@Slf4j
public abstract class AbstractCalculator<R, C extends Context> {

    /**
     * 获取计算器名称
     *
     * @return 返回值
     */
    protected abstract String getName();

    /**
     * 执行计算
     *
     * @param ctx
     * @return 返回计算结果
     */
    protected abstract R doExecute(C ctx);

    /**
     * 执行计算
     *
     * @param context
     * @return
     */
    protected R execute(C context) {
        R result = doExecute(context);
        log.info("[{}]|{}|{}", getName(), JsonUtils.toJson(result), JsonUtils.toJson(context));
        return result;
    }
}

3.3.2.2、DeductAmountCalculator

@Service
@Slf4j
public class DeductAmountCalculator extends AbstractProxyLiveCalculator {

    @Override
    protected String getName() {
        return DeductAmountCalculator.class.getSimpleName();
    }

    @Override
    protected Integer doExecute(AbstractCalculateContext ctx) {
        DeductAmountCalculateContext context = (DeductAmountCalculateContext) ctx;
        JobApplyLiveBO jobApplyLive = context.getJobApplyLive();
        JobPoolBO jobPool = context.getJobPool();
        List<String> proxyList = getProxyJobList(jobApplyLive);
        int proxyCount = proxyList.size();
        int finishedCount = jobPool.getDeliveryMatchCount();
        UserOrderInfoBO userOrderInfoBO = findOrder(jobApplyLive.getId());
        int deductAmount = 0;
        int buyAmount = Optional.ofNullable(userOrderInfoBO.getBuyAmount()).orElse(BigDecimal.ZERO).intValue();
        int payAmount = Optional.ofNullable(userOrderInfoBO.getPayAmount()).orElse(BigDecimal.ZERO).intValue();
        if (finishedCount < proxyCount - 1) {
            deductAmount = Math.round((float) buyAmount / proxyCount);
        }
        if (finishedCount == proxyCount - 1) {
            deductAmount = buyAmount - payAmount;
        }
        JSONObject extend = new JSONObject();
        extend.put("applyId", jobApplyLive.getId());
        extend.put("proxyCount", proxyCount);
        extend.put("finishedCount", finishedCount);
        extend.put("deductAmount", deductAmount);
        ctx.setExtend(extend);
        return deductAmount;
    }
}

3.3.3、Context

3.3.3.1、AbstractCalculateContext

该抽象类封装了相关场景所依赖的共同参数,相关子类只需要继承此基类,然后扩展自己的成员属性即可。

/**
 * 抽象计算上下文
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/17 9:52 AM
 */
@Data
@SuperBuilder
public class AbstractCalculateContext implements Context {

    /**
     * 申请单
     */
    private JobApplyLiveBO jobApplyLive;

    /**
     * 职位池
     */
    private JobPoolBO jobPool;

    /**
     * 扩展项
     */
    private JSONObject extend;
}

3.3.3.2、PartialDeductAmountCalculateContext

/**
 * 计算部分划扣金额上下文
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/17 9:52 AM
 */
@Data
@SuperBuilder
public class PartialDeductAmountCalculateContext extends AbstractCalculateContext {

    /**
     * 当前要部分交付完成退款金额
     */
    private int presentRefundAmount;
}

3.3.4、CalculateMediator

相关计算器统一通过Spring @Autowired注入到该类,作为其成员属性。该类相当于一个委托类,并提供一系列成员方法,提供业务场景的金额计算调用,业务场景不需要关注具体选择何种计算器,只需要交由其处理。做到计算器与业务场景的隔离,方便后期扩展维护。

@Component
@Slf4j
public class JobApplyLiveCalculateMediator {

    @Autowired
    private DeductAmountCalculator deductAmountCalculator;

    @Autowired
    private RefundAmountCalculator refundAmountCalculator;

    @Autowired
    private PartialDeductAmountCalculator partialDeductAmountCalculator;

    @Autowired
    private DeliveryDeductAmountCalculator deliveryDeductAmountCalculator;

	//示例代码方法1
    public int calculateDeductAmount(JobApplyLiveBO jobApplyLive, JobPoolBO jobPool) {
        DeductAmountCalculateContext context = DeductAmountCalculateContext.builder().jobApplyLive(jobApplyLive).jobPool(jobPool).build();
        return deductAmountCalculator.execute(context);
    }

   //省略方法2、3、4、5
}

3.4、付款与退款的设计

原创|关于一次产品需求程序设计及优化的经历_第11张图片

3.4.1、Context类的设计

/**
 * 交易处理上下文对象
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:24 AM
 */
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@Data
public class TransactionHandleContext implements Context {

    /**
     * 请求参数,需要外部调用实例化
     */
    private Request request;

    /**
     * 计算结果,内部处理使用
     */
    private CalculateResult calculateResult;

    /**
     * 付款请求参数
     */
    private PaymentRequestBO paymentRequest;

    /**
     * 订单信息
     */
    private UserOrderInfoBO userOrderInfo;

    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    @Data
    public static class Request {

        /**
         * 产品策略
         */
        protected ProductStrategyEnum productStrategy;

        /**
         * 业务类型
         */
        private BusinessTypeEnum businessType;

        /**
         * 交易类型
         */
        private TransactionTypeEnum transactionType;
    }

    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    @Data
    public static class CalculateResult {

        /**
         * 确认
         */
        private BigDecimal confirmed;

        /**
         * 累计付款
         */
        private BigDecimal paid;

        /**
         * 累计退款
         */
        private BigDecimal refunded;

        /**
         * 是否满足结算完成
         */
        private boolean matchSettle;
    }
}

3.4.2、TransactionHandler类的设计

/**
 * 抽象交易处理处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:26 AM
 */
@Slf4j
public abstract class AbstractTransactionHandler implements Handle<TransactionHandleContext> {

    protected final static String LOG_PREFIX = "[TransactionHandler]";

    /**
     * 产品策略
     */
    protected ProductStrategyEnum productStrategy;

    /**
     * 交易类型
     */
    private TransactionTypeEnum transactionType;

    public AbstractTransactionHandler(ProductStrategyEnum productStrategy, TransactionTypeEnum transactionType) {
        this.productStrategy = productStrategy;
        this.transactionType = transactionType;
    }

    @Autowired
    protected UserOrderInfoDAO userOrderInfoDAO;

    @Autowired
    protected PaymentBusiness paymentBusiness;

    @Autowired
    protected UserOrderInfoDetailDAO userOrderInfoDetailDAO;

    @Autowired
    protected UserOrderInfoDetailConverter userOrderInfoDetailConverter;

    @Autowired
    private RedisClient redisClient;

    /**
     * 执行订单检查
     *
     * @param context 上下文对象
     * @return 返回校验结果
     */
    protected abstract CheckedResult doCheckOrder(TransactionHandleContext context);

    /**
     * 执行计算
     *
     * @param context 上下文对象
     */
    protected abstract void doCalculate(TransactionHandleContext context);

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    protected abstract void doExecute(TransactionHandleContext context);

    /**
     * 获取加锁key
     *
     * @param context 上下文对象
     * @return 返回值
     */
    protected String getLockKey(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        return "confirmPayment:lock:" + paymentRequestBO.getBusinessId() + "-" + paymentRequestBO.getBusinessType();
    }

    /**
     * 执行数据装载
     *
     * @param context 上下文对象
     */
    protected void doPrepare(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfoBO = getUserOrderInfo(paymentRequestBO.getBusinessId(), paymentRequestBO.getBusinessType());
        context.setUserOrderInfo(userOrderInfoBO);
    }

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    @Override
    public void execute(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        String key = getLockKey(context);
        redisClient.lock(() -> {
            try {
                doExecute(context);
            } catch (Exception e) {
                log.error("{}|{}|param={}", LOG_PREFIX, getName(), JsonUtils.toJson(paymentRequestBO), e);
            }
        }, key);
    }

    private UserOrderInfoBO getUserOrderInfo(long businessId, int businessType) {
        UserOrderInfoBO userOrderInfoBO = new UserOrderInfoBO();
        userOrderInfoBO.setBusinessId(businessId);
        userOrderInfoBO.setBusinessType(businessType);
        List<UserOrderInfoBO> list = userOrderInfoDAO.list(userOrderInfoBO);
        if (CollectionUtils.isEmpty(list)) {
            throw new BusinessException("支付订单不存在!");
        }
        return list.get(0);
    }

    protected void addOrderDetail(PaymentRequestBO paymentRequestBO, Long orderId) {
        UserOrderInfoDetailBO bo = userOrderInfoDetailConverter.convertToUserOrderInfoDetailBO(paymentRequestBO, orderId);
        userOrderInfoDetailDAO.insert(bo);
    }

    public ProductStrategyEnum getProductStrategy() {
        return productStrategy;
    }

    public TransactionTypeEnum getTransactionType() {
        return transactionType;
    }

    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Data
    public static class CheckedResult {

        /**
         * 校验成功
         */
        private boolean success;

        /**
         * 校验消息
         */
        private String message;
    }
}

3.4.3、PaymentTransactionHandler类的设计

/**
 * 抽象付款处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:26 AM
 */
@Slf4j
public abstract class AbstractPaymentTransactionHandler extends AbstractTransactionHandler {

    public AbstractPaymentTransactionHandler(ProductStrategyEnum productStrategy) {
        super(productStrategy, TransactionTypeEnum.PAYMENT);
    }

    @Override
    public void doExecute(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        doPrepare(context);
        UserOrderInfoBO userOrderInfoBO = context.getUserOrderInfo();
        String trans = userOrderInfoBO.getPaymentNo();
        Long bizId = userOrderInfoBO.getBusinessId();
        if (userOrderInfoBO.getState() == PayStatusEnum.SUCCESS.getValue()) {
            return;
        }
        if (userOrderInfoBO.getState() != PayStatusEnum.FREEZE.getValue()) {
            throw new BusinessException("订单不是冻结状态,无法确认支付!");
        }
        CheckedResult checked = doCheckOrder(context);
        if (!checked.isSuccess()) {
            throw new BusinessException(checked.getMessage());
        }
        doCalculate(context);
        ConfirmPaymentBO confirmPaymentBO = doBuildConfirmPayment(context);
        log.info("{}|{}|trans={}|bizId={}|Calculate={}|confirmPayment-pay:{}", LOG_PREFIX, getName(),trans, bizId, JsonUtils.toJson(context.getCalculateResult()), JsonUtils.toJson(confirmPaymentBO));
        boolean result = paymentBusiness.confirmPayment(confirmPaymentBO);
        if (!result) {
            throw new BusinessException("订单确认支付失败!");
        }
        UserOrderInfoBO updateUserOrderInfoBO = doBuildUserOrderInfo(context);
        userOrderInfoDAO.update(updateUserOrderInfoBO);
        addOrderDetail(paymentRequestBO, userOrderInfoBO.getId());
    }

    protected ConfirmPaymentBO doBuildConfirmPayment(TransactionHandleContext context) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        ConfirmPaymentBO confirmPaymentBO = new ConfirmPaymentBO();
        confirmPaymentBO.setUserOrderInfoBO(context.getUserOrderInfo());
        confirmPaymentBO.setConfirmType(ConfirmTypeEnum.valueOf(userOrderInfo.getConfirmType()));
        confirmPaymentBO.setConfirmed(calculateResult.getConfirmed().longValue());
        return confirmPaymentBO;
    }

    protected UserOrderInfoBO doBuildUserOrderInfo(TransactionHandleContext context) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        UserOrderInfoBO updateUserOrderInfo = new UserOrderInfoBO();
        updateUserOrderInfo.setId(context.getUserOrderInfo().getId());
        if (calculateResult.isMatchSettle()) {
            updateUserOrderInfo.setState(PayStatusEnum.FINISH.getValue());
        }
        invokeUserOrderInfoSet(context, updateUserOrderInfo);
        return updateUserOrderInfo;
    }

    /**
     * 设置需要更新订单的相关属性
     *
     * @param context 上下文信息
     * @param orderInfo 订单信息
     */
    protected abstract void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo);
}

3.4.3.1、StandardPricePaymentTransactionHandler类的设计

/**
 * (按商品价格)付款交易处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:31 AM
 */
@Service
@Slf4j
public class StandardPricePaymentTransactionHandler extends AbstractPaymentTransactionHandler {

    public StandardPricePaymentTransactionHandler() {
        super(ProductStrategyEnum.STANDARD_PRICE);
    }

    @Override
    public String getName() {
        return StandardPricePaymentTransactionHandler.class.getSimpleName();
    }

    @Override
    protected CheckedResult doCheckOrder(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal rest = buyAmount.subtract(payAmount).subtract(cancelAmount);
        if (rest.compareTo(operateAmount) == -1) {
            return CheckedResult.builder().success(Boolean.FALSE).message("支付金额大于剩余金额,无法确认支付").build();
        }
        return CheckedResult.builder().success(Boolean.TRUE).build();
    }

    @Override
    protected void doCalculate(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal total = payAmount.add(operateAmount).add(cancelAmount);
        boolean matchSettle = total.compareTo(buyAmount) == 0;
        TransactionHandleContext.CalculateResult calculateResult = TransactionHandleContext.CalculateResult.builder()
                .confirmed(payAmount.add(operateAmount))
                .paid(payAmount.add(operateAmount))
                .matchSettle(matchSettle)
                .build();
        context.setCalculateResult(calculateResult);
    }

    @Override
    protected void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        orderInfo.setPayAmount(calculateResult.getPaid());
    }
}

3.4.3.2、StandardPricePaymentTransactionHandler类的设计

/**
 * (按商品价格)付款交易处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:31 AM
 */
@Service
@Slf4j
public class StandardPricePaymentTransactionHandler extends AbstractPaymentTransactionHandler {

    public StandardPricePaymentTransactionHandler() {
        super(ProductStrategyEnum.STANDARD_PRICE);
    }

    @Override
    public String getName() {
        return StandardPricePaymentTransactionHandler.class.getSimpleName();
    }

    @Override
    protected CheckedResult doCheckOrder(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal rest = buyAmount.subtract(payAmount).subtract(cancelAmount);
        if (rest.compareTo(operateAmount) == -1) {
            return CheckedResult.builder().success(Boolean.FALSE).message("支付金额大于剩余金额,无法确认支付").build();
        }
        return CheckedResult.builder().success(Boolean.TRUE).build();
    }

    @Override
    protected void doCalculate(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal total = payAmount.add(operateAmount).add(cancelAmount);
        boolean matchSettle = total.compareTo(buyAmount) == 0;
        TransactionHandleContext.CalculateResult calculateResult = TransactionHandleContext.CalculateResult.builder()
                .confirmed(payAmount.add(operateAmount))
                .paid(payAmount.add(operateAmount))
                .matchSettle(matchSettle)
                .build();
        context.setCalculateResult(calculateResult);
    }

    @Override
    protected void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        orderInfo.setPayAmount(calculateResult.getPaid());
    }
}

3.4.4、TransactionHandler类的设计

/**
 * 抽象退款处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:26 AM
 */
@Slf4j
public abstract class AbstractRefundTransactionHandler extends AbstractTransactionHandler {

    public AbstractRefundTransactionHandler(ProductStrategyEnum productStrategy) {
        super(productStrategy, TransactionTypeEnum.REFUND);
    }

    @Override
    public void doExecute(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        doPrepare(context);
        UserOrderInfoBO userOrderInfoBO = context.getUserOrderInfo();
        String trans = userOrderInfoBO.getPaymentNo();
        Long bizId = userOrderInfoBO.getBusinessId();
        if (userOrderInfoBO.getState() != PayStatusEnum.FREEZE.getValue()) {
            throw new BusinessException("订单不是冻结状态,无法取消支付!");
        }
        CheckedResult checked = doCheckOrder(context);
        if (!checked.isSuccess()) {
            throw new BusinessException(checked.getMessage());
        }
        doCalculate(context);
        ConfirmPaymentBO confirmPaymentBO = doBuildConfirmPayment(context);
        log.info("{}|{}|trans={}|bizId={}|Calculate={}|confirmPayment-cancel:{}", LOG_PREFIX, getName(), trans, bizId, JsonUtils.toJson(context.getCalculateResult()), JsonUtils.toJson(confirmPaymentBO));
        boolean result = paymentBusiness.confirmPayment(confirmPaymentBO);
        if (!result) {
            throw new BusinessException("订单取消支付失败!");
        }
        UserOrderInfoBO updateUserOrderInfoBO = doBuildUserOrderInfo(context);
        userOrderInfoDAO.update(updateUserOrderInfoBO);
        addOrderDetail(paymentRequestBO, userOrderInfoBO.getId());
    }

    protected ConfirmPaymentBO doBuildConfirmPayment(TransactionHandleContext context) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        ConfirmPaymentBO confirmPaymentBO = new ConfirmPaymentBO();
        confirmPaymentBO.setUserOrderInfoBO(context.getUserOrderInfo());
        confirmPaymentBO.setConfirmType(ConfirmTypeEnum.valueOf(userOrderInfo.getConfirmType()));
        confirmPaymentBO.setRefunded(calculateResult.getConfirmed().longValue());
        return confirmPaymentBO;
    }

    protected UserOrderInfoBO doBuildUserOrderInfo(TransactionHandleContext context) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        UserOrderInfoBO updateUserOrderInfo = new UserOrderInfoBO();
        updateUserOrderInfo.setId(context.getUserOrderInfo().getId());
        if (calculateResult.isMatchSettle()) {
            updateUserOrderInfo.setState(PayStatusEnum.FINISH.getValue());
        }
        invokeUserOrderInfoSet(context, updateUserOrderInfo);
        return updateUserOrderInfo;
    }

    /**
     * 设置需要更新订单的相关属性
     *
     * @param context   上下文信息
     * @param orderInfo 订单信息
     */
    protected abstract void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo);
}

3.4.4.1、StandardPriceRefundTransactionHandler类的设计

/**
 * (按商品价格)退款交易处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:31 AM
 */
@Service
@Slf4j
public class StandardPriceRefundTransactionHandler extends AbstractRefundTransactionHandler {

    public StandardPriceRefundTransactionHandler() {
        super(ProductStrategyEnum.STANDARD_PRICE);
    }

    @Override
    public String getName() {
        return StandardPriceRefundTransactionHandler.class.getSimpleName();
    }

    @Override
    protected CheckedResult doCheckOrder(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal rest = buyAmount.subtract(payAmount).subtract(cancelAmount);
        if (rest.compareTo(operateAmount) == -1) {
            return CheckedResult.builder().success(Boolean.FALSE).message("支付金额大于剩余金额,无法确认支付").build();
        }
        return CheckedResult.builder().success(Boolean.TRUE).build();
    }

    @Override
    protected void doCalculate(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal total = payAmount.add(operateAmount).add(cancelAmount);
        boolean matchSettle = total.compareTo(buyAmount) == 0;
        TransactionHandleContext.CalculateResult calculateResult = TransactionHandleContext.CalculateResult.builder()
                .confirmed(cancelAmount.add(operateAmount))
                .refunded(cancelAmount.add(operateAmount))
                .matchSettle(matchSettle)
                .build();
        context.setCalculateResult(calculateResult);
    }

    @Override
    protected void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        orderInfo.setCancelAmount(calculateResult.getRefunded());
    }
}

3.4.4.2、StandardQuantityRefundTransactionHandler类的设计

/**
 * (按商品数量)退款交易处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:31 AM
 */
@Service
@Slf4j
public class StandardQuantityRefundTransactionHandler extends AbstractRefundTransactionHandler {

    public StandardQuantityRefundTransactionHandler() {
        super(ProductStrategyEnum.STANDARD_QUANTITY);
    }

    @Override
    public String getName() {
        return StandardQuantityRefundTransactionHandler.class.getSimpleName();
    }

    @Override
    protected CheckedResult doCheckOrder(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        int buyCount = Optional.ofNullable(userOrderInfo.getBuyCount()).orElse(0);
        int payCount = Optional.ofNullable(userOrderInfo.getPayCount()).orElse(0);
        int cancelCount = Optional.ofNullable(userOrderInfo.getCancelCount()).orElse(0);
        int operateCount = Optional.ofNullable(paymentRequestBO.getOperateCount()).orElse(0);
        if ((buyCount - payCount - cancelCount) < operateCount) {
            return CheckedResult.builder().success(Boolean.FALSE).message("支付数大于剩余数量,无法确认支付").build();
        }
        return CheckedResult.builder().success(Boolean.TRUE).build();
    }

    @Override
    protected void doCalculate(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        int buyCount = Optional.ofNullable(userOrderInfo.getBuyCount()).orElse(0);
        int payCount = Optional.ofNullable(userOrderInfo.getPayCount()).orElse(0);
        int cancelCount = Optional.ofNullable(userOrderInfo.getCancelCount()).orElse(0);
        int operateCount = Optional.ofNullable(paymentRequestBO.getOperateCount()).orElse(0);
        int total = payCount + operateCount + cancelCount;
        boolean matchSettle = total == buyCount;
        TransactionHandleContext.CalculateResult calculateResult = TransactionHandleContext.CalculateResult.builder()
                .confirmed(BigDecimal.valueOf(cancelCount + operateCount))
                .refunded(BigDecimal.valueOf(cancelCount + operateCount))
                .matchSettle(matchSettle)
                .build();
        context.setCalculateResult(calculateResult);
    }

    @Override
    protected void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        orderInfo.setCancelCount(calculateResult.getRefunded().intValue());
    }
}

四、优化设计

1、计算模型优化

第一版的设计,有这么一个JobApplyLiveCalculator类,里边有若干个成员方法,但是缺点就是后续如果增加场景,就会增加再增加一个成员方法,同时,如果方法增加参数,意味着相关调用方都需要增加参数,意味着耦合性强,因此为了达到松耦合,符合单一原则,抽象了如下领域模型。

原创|关于一次产品需求程序设计及优化的经历_第12张图片

  • Context:上下文类封装各个场景所需要的相关参数,同时抽象出一个AbstractContext类,各个场景继承此类。
  • Calculator:抽象出AbstractCalculator,各个场景有对应的计算器完成计算。
  • Mediator:委托类,只能处理委托给相关场景的Calculator完成处理,返回把计算的结果再返回给调用方。

2、Dispatcher优化

该类具体就是完成业务场景处理器的路由与分发,场景不需要关注选择何种处理器,只需要委托该类处理,做到职责隔离,方便后期扩展。

原创|关于一次产品需求程序设计及优化的经历_第13张图片

2.1、第一版Dispatcher设计

/**
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 2:18 PM
 */
@Component
@Slf4j
public class TransactionHandleDispatcher {

    @Autowired
    private List<AbstractTransactionHandler> handlers;

    /**
     * 执行分发策略
     *
     * @param context 上下文对象
     */
    public void execute(TransactionHandleContext context) {
        handlers.stream().forEach(each -> {
        Optional<AbstractTransactionHandler> optional = handlers.stream().filter(each -> {
            TransactionHandleContext.Request request = context.getRequest();
            boolean find = request.getProductStrategy() == each.getProductStrategy()
                    && request.getTransactionType() == each.getTransactionType();
            if (find) {
                each.execute(context);
            }
        });
            return request.getProductStrategy() == each.getProductStrategy() && request.getTransactionType() == each.getTransactionType();
        }).findAny();
        if (optional.isPresent()) {
            optional.get().execute(context);
        } else {
            log.error("[TransactionHandle]未找到对应的处理器", JsonUtils.toJson(context));
        }
    }
}

由于各个模块都有对应的Dispatcher,但是该类仔细观察,还可以继续抽象。通过分析,发现这个类有以下几个特征:

  • 通过Spring @Autowired自动装配相关处理类的子类,子类具有有哪些,这个类不需要关注。
  • 对外提供的execute方法,返回类型void,入参是一个Context类。
  • execute方法内部,需要根据Context类的实例化参数来进行选择一个Handler来处理。

通过分析如上特征,我认为可以对Dispatcher再进行抽象,所有的上下文类提取一个Context接口,所有的处理器类实现一个Handler接口。

2.2、第二版Dispatcher设计

目标:基于面向接口编程的设计。

2.2.1、Context接口

该接口没有成员方法,它代表一种上下文能力特征,只要具备此特征就需要实现该接口。

/**
 * 上下文接口
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 7:14 AM
 */
public interface Context {
}

2.2.2、Handle接口

该接口两个成员方法,同时该类规约了上下文类需要实现Context接口。

/**
 * 处理接口
 * 适用于多场景、多策略的处理器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 6:46 AM
 */
public interface Handle<C extends Context> {
    /**
     * 获取处理器名称
     *
     * @return 处理器名称
     */
    String getName();

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    void execute(C context);
}

2.2.3、AbstractDispatcher抽象类

只要实现Handle接口的类,同时具备Spring Bean容器管理的相关处理器Handler都可以通@Autowired完成应用成员启动时自动装配。对于子类而言,主要实现find方法,完成从getHander()方法中获取处理器集合,选择一个场景匹配的处理器完成处理。

/**
 * 抽象处理分发器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 6:42 AM
 */
@Slf4j
public abstract class AbstractHandleDispatcher<H extends Handle, C extends Context> {
    /**
     * 获取分发器名称
     *
     * @return 分发器名称
     */
    protected abstract String getName();

    /**
     * 获取处理器集合
     *
     * @return 处理器集合
     */
    protected abstract List<H> getHandlers();

    /**
     * 查找处理器
     *
     * @param context 处理器上下文
     * @return 要分发的处理器
     */
    protected abstract Optional<H> find(C context);

    /**
     * 执行分发
     *
     * @param context 处理器上下文
     */
    public void execute(C context) {
        Optional<H> optional = find(context);
        if (optional.isPresent()) {
            optional.get().execute(context);
        } else {
            log.error("no find handler", JsonUtils.toJson(context));
        }
    }
}

具体一个Dispatcher类,只需要继承此类,实现抽象方法即可。

/**
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 2:18 PM
 */
@Component
@Slf4j
public class TransactionHandleDispatcher extends AbstractHandleDispatcher<AbstractTransactionHandler, TransactionHandleContext> {

    @Autowired
    private List<AbstractTransactionHandler> handlers;

    @Override
    protected String getName() {
        return TransactionHandleDispatcher.class.getSimpleName();
    }

    @Override
    protected List<AbstractTransactionHandler> getHandlers() {
        return handlers;
    }

    @Override
    protected Optional<AbstractTransactionHandler> find(TransactionHandleContext context) {
        return getHandlers().stream().filter(each -> {
            TransactionHandleContext.Request request = context.getRequest();
            return request.getProductStrategy() == each.getProductStrategy() && request.getTransactionType() == each.getTransactionType();
        }).findAny();
    }
}

五、总结

谈言之,这个需求总体涉及变动点多,为了考虑尽可能代码变动影响面小,所以经过慎重选择,设计出一种利弊均衡的技术方案设计,这种设计同时达成了一个目标,业务场景与底层能力解耦,能力通过抽象下沉,委托给Dispatcher或者Mediator完成具体处理器Handler的分发和处理。

恰如一个需求来了,有个项目经理来完成跟产品经理对接,产品经理不需要关注后端、前端是哪位成员,他只需要跟项目经理对接即可,做到隔离。如果后端有成员变更,对于产品经理做到隔离,产品经理无需关注后端有成员变更,他只需要关注目标的达成,通过接口隔离即可实现。

同时,这个需求也让我从零熟悉了整个业务流程,它不仅是一种挑战,更是一种成长。

你可能感兴趣的:(设计模式活用,方案设计,java,程序设计,设计模式)