基础回顾

  • 模板方法是对一系列逻辑的抽象,通常我们会使用一个抽象类,将它子类的通用逻辑抽象到这个抽象类中,并赋予它们具体的实现。同时会抽象出一些抽象逻辑,这些抽象逻辑不赋予具体实现,而是以抽象方法的形式存在于抽象类中。当我们需要这一类的实现类时,直接继承它的抽象类,重载抽象方法即可。这样做的好处是利用了抽象这一面向对象的设计原则,提升代码的可读性和可维护性。
  • 在一个系统中,如果多个模块频繁修改另一个模块A的数据,此时可以使用命令模式,将修改的操作抽象为一个个命令。结合模板方法模式,可以将多个命令的共同点抽取出来,减少重复代码。
  • 工厂方法模式主要是用来创建复杂对象,可以使用工厂创建一个个的命令。

库存管理案例背景

  • 电商中的库存中心很适合使用这几种模式的组合。因为无论是采购入库加库存、下单减库存、退货加库存,都需要对库存中心的各种库存字段进行修改操作。虽然这些修改操作的入参不同,但是都要遵循一定的步骤,例如更新销售库存,更新锁定库存,更新已售数量,更新库存状态等。
  • 可以将这些更新库存的操作步骤抽象成一个个命令,然后基于这些命令更新库存,库存中心对外(WMS中心,订单中心,支付中心等)暴露的只是一个个接口,这些中心无需关心库存中心是怎么实现的。
  • 如果不使用命令+模板方法,这些更新操作会散落在一个个中心里,如果后续库存中心表进行了修改,这些中心还要关心自己应该如何随着库存中心的变化而变化。

实现

  • 定义接口。StockUpdater是所有命令的顶层接口。StockUpdaterFactory<T>是所有命令工厂的顶层接口。
  1. public interface StockUpdater {
  2. /**
  3. * 更新商品库存
  4. */
  5. Boolean updateGoodsStock();
  6. }
  7. public interface StockUpdaterFactory<T> {
  8. /**
  9. * 创建一个库存更新命令
  10. * @param parameter 参数对象
  11. */
  12. StockUpdater create(T parameter);
  13. }
  • 定义抽象类。AbstractStockUpdater是所有命令的抽象类。它同时也是一个模板:规定更新一个商品库存的时候,要考虑它的销售库存、锁定库存、已售库存、库存状态和更新时间。对于不同的更新命令,都可以选择性地设置这几个字段的值,最后再执行更新操作。其中,更新库存状态和修改时间这两步,逻辑是固定的,所以声明为普通方法,其他步骤声明为抽象方法,给各个子类去重载。
  1. public abstract class AbstractStockUpdater implements StockUpdater {
  2. private static final Logger logger = LoggerFactory.getLogger(
  3. AbstractStockUpdater.class);
  4. /**
  5. * 商品库存DO对象
  6. */
  7. protected List<GoodsStockDO> goodsStockDOs;
  8. /**
  9. * 商品库存管理模块的DAD组件
  10. */
  11. protected GoodsStockDAO goodsStockDAO;
  12. /**
  13. * 日期辅助组件
  14. */
  15. protected DateProvider dateProvider;
  16. /**
  17. * 构造函数
  18. * @param goodsStockDAO 商品库存管理模块的DAO组件
  19. * @param dateProvider 日期辅助组件
  20. */
  21. public AbstractStockUpdater(
  22. List<GoodsStockDO> goodsStockDOs,
  23. GoodsStockDAO goodsStockDAO,
  24. DateProvider dateProvider) {
  25. this.goodsStockDOs = goodsStockDOs;
  26. this.goodsStockDAO = goodsStockDAO;
  27. this.dateProvider = dateProvider;
  28. }
  29. /**
  30. * 更新商品库存
  31. */
  32. @Override
  33. public Boolean updateGoodsStock() {
  34. try {
  35. updateSaleStockQuantity();
  36. updateLockedStockQuantity();
  37. updateSaledStockQuantity();
  38. updateStockStatus();
  39. updateGmtModified();
  40. executeUpdateGoodsStock();
  41. } catch (Exception e) {
  42. logger.error("error", e);
  43. }
  44. return true;
  45. }
  46. /**
  47. * 更新商品的销售库存
  48. * @throws Exception
  49. */
  50. protected abstract void updateSaleStockQuantity() throws Exception;
  51. /**
  52. * 更新商品的锁定库存
  53. * @throws Exception
  54. */
  55. protected abstract void updateLockedStockQuantity() throws Exception;
  56. /**
  57. * 更新商品的已销售库存
  58. * @throws Exception
  59. */
  60. protected abstract void updateSaledStockQuantity() throws Exception;
  61. /**
  62. * 更新商品的库存状态
  63. */
  64. private void updateStockStatus() throws Exception {
  65. for(GoodsStockDO goodsStockDO : goodsStockDOs) {
  66. if(goodsStockDO.getSaleStockQuantity() > 0L) {
  67. goodsStockDO.setStockStatus(StockStatus.IN_STOCK);
  68. } else {
  69. goodsStockDO.setStockStatus(StockStatus.NOT_IN_STOCK);
  70. }
  71. }
  72. }
  73. /**
  74. * 更新商品库存的修改时间
  75. */
  76. private void updateGmtModified() throws Exception {
  77. for(GoodsStockDO goodsStockDO : goodsStockDOs) {
  78. goodsStockDO.setGmtModified(dateProvider.getCurrentTime());
  79. }
  80. }
  81. /**
  82. * 最后执行的更新商品库存的操作
  83. * @throws Exception
  84. */
  85. private void executeUpdateGoodsStock() throws Exception {
  86. for(GoodsStockDO goodsStockDO : goodsStockDOs) {
  87. goodsStockDAO.updateGoodsStock(goodsStockDO);
  88. }
  89. }
  90. }
  • 创建生产命令的工厂AbstractStockUpdaterFactory。对于订单中心或者WMS中心传入的订单对象或入库对象等参数,库存中心需要将其与自己数据库中的库存对象相对应。这个生产Map的逻辑在子类工厂中的create()方法中处理。
  1. public abstract class AbstractStockUpdaterFactory<T>
  2. implements StockUpdaterFactory<T> {
  3. private static final Logger logger = LoggerFactory.getLogger(
  4. AbstractStockUpdaterFactory.class);
  5. /**
  6. * 商品库存管理模块的DAO组件
  7. */
  8. protected GoodsStockDAO goodsStockDAO;
  9. /**
  10. * 日期辅助组件
  11. */
  12. protected DateProvider dateProvider;
  13. /**
  14. * 构造函数
  15. * @param goodsStockDAO 商品库存管理模块的DAO组件
  16. * @param dateProvider 日期辅助组件
  17. */
  18. public AbstractStockUpdaterFactory(
  19. GoodsStockDAO goodsStockDAO,
  20. DateProvider dateProvider) {
  21. this.goodsStockDAO = goodsStockDAO;
  22. this.dateProvider = dateProvider;
  23. }
  24. /**
  25. * 创建库存更新命令
  26. */
  27. @Override
  28. public StockUpdater create(T parameter) {
  29. try {
  30. List<Long> goodsSkuIds = getGoodsSkuIds(parameter);
  31. List<GoodsStockDO> goodsStockDOs = createGoodsStockDOs(goodsSkuIds);
  32. return create(goodsStockDOs, parameter);
  33. } catch (Exception e) {
  34. logger.error("error", e);
  35. }
  36. return null;
  37. }
  38. /**
  39. * 获取商品sku id集合
  40. * @param parameter 参数
  41. * @return 商品sku id集合
  42. * @throws Exception
  43. */
  44. protected abstract List<Long> getGoodsSkuIds(T parameter) throws Exception;
  45. /**
  46. * 创建库存更新命令
  47. * @param parameter 参数
  48. * @param goodsStockDOs 商品库存DO对象集合
  49. * @return 库存更新命令
  50. * @throws Exception
  51. */
  52. protected abstract StockUpdater create(
  53. List<GoodsStockDO> goodsStockDOs, T parameter) throws Exception;
  54. /**
  55. * 创建商品库存DO对象集合
  56. * @param goodsSkuIds 商品sku id集合
  57. * @return 商品库存DO对象集合
  58. */
  59. private List<GoodsStockDO> createGoodsStockDOs(List<Long> goodsSkuIds) throws Exception {
  60. List<GoodsStockDO> goodsStocks = new ArrayList<GoodsStockDO>(goodsSkuIds.size());
  61. for(Long goodsSkuId : goodsSkuIds) {
  62. GoodsStockDO goodsStock = goodsStockDAO.getGoodsStockBySkuId(goodsSkuId);
  63. if(goodsStock == null) {
  64. goodsStock = createGoodsStock(goodsSkuId);
  65. goodsStockDAO.saveGoodsStock(goodsStock);
  66. }
  67. goodsStocks.add(goodsStock);
  68. }
  69. return goodsStocks;
  70. }
  71. /**
  72. * 创建商品库存DO对象
  73. * @param goodsSkuId 商品sku id
  74. * @return 商品库存DO对象
  75. * @throws Exception
  76. */
  77. private GoodsStockDO createGoodsStock(Long goodsSkuId) throws Exception {
  78. GoodsStockDO goodsStockDO = new GoodsStockDO();
  79. goodsStockDO.setGoodsSkuId(goodsSkuId);
  80. goodsStockDO.setSaleStockQuantity(0L);
  81. goodsStockDO.setLockedStockQuantity(0L);
  82. goodsStockDO.setSaledStockQuantity(0L);
  83. goodsStockDO.setStockStatus(StockStatus.NOT_IN_STOCK);
  84. goodsStockDO.setGmtCreate(dateProvider.getCurrentTime());
  85. goodsStockDO.setGmtModified(dateProvider.getCurrentTime());
  86. return goodsStockDO;
  87. }
  88. }

使用

  • 现在订单中心提交订单了,需要库存中心更新库存,具体来说是:减销售库存、增加锁定库存。此时我们创建一个SubmitOrderStockUpdater,作为库存中心接受订单中心的命令。然后基于这个命令,更新库存。当然,它还需要一个工厂,为它将订单对象转换为自己可以操作的库存对象,并为其注入相关依赖。
  • 创建提交订单的命令。
  1. public class SubmitOrderStockUpdater extends AbstractStockUpdater {
  2. /**
  3. * 订单条目DTO对象集合
  4. */
  5. private Map<Long, OrderItemDTO> orderItemDTOMap;
  6. /**
  7. * 构造函数
  8. * @param goodsStockDOs 商品库存DO对象集合
  9. * @param goodsStockDAO 商品库存管理模块DAO组件
  10. * @param dateProvider 日期辅助组件
  11. */
  12. public SubmitOrderStockUpdater(
  13. List<GoodsStockDO> goodsStockDOs,
  14. GoodsStockDAO goodsStockDAO,
  15. DateProvider dateProvider,
  16. Map<Long, OrderItemDTO> orderItemDTOMap) {
  17. super(goodsStockDOs, goodsStockDAO, dateProvider);
  18. this.orderItemDTOMap = orderItemDTOMap;
  19. }
  20. /**
  21. * 更新销售库存
  22. */
  23. @Override
  24. protected void updateSaleStockQuantity() throws Exception {
  25. for(GoodsStockDO goodsStockDO : goodsStockDOs) {
  26. OrderItemDTO orderItemDTO = orderItemDTOMap.get(goodsStockDO.getGoodsSkuId());
  27. goodsStockDO.setSaleStockQuantity(goodsStockDO.getSaleStockQuantity()
  28. - orderItemDTO.getPurchaseQuantity());
  29. }
  30. }
  31. /**
  32. * 更新锁定库存
  33. */
  34. @Override
  35. protected void updateLockedStockQuantity() throws Exception {
  36. for(GoodsStockDO goodsStockDO : goodsStockDOs) {
  37. OrderItemDTO orderItemDTO = orderItemDTOMap.get(goodsStockDO.getGoodsSkuId());
  38. goodsStockDO.setLockedStockQuantity(goodsStockDO.getLockedStockQuantity()
  39. + orderItemDTO.getPurchaseQuantity());
  40. }
  41. }
  42. /**
  43. * 更新已销售库存
  44. */
  45. @Override
  46. protected void updateSaledStockQuantity() throws Exception {
  47. }
  48. }
  • 创建工厂SubmitOrderStockUpdaterFactory<T>
  1. @Component
  2. public class SubmitOrderStockUpdaterFactory<T>
  3. extends AbstractStockUpdaterFactory<T> {
  4. /**
  5. * 构造函数
  6. * @param goodsStockDAO 商品库存管理模块DAO组件
  7. * @param dateProvider 日期辅助组件
  8. */
  9. @Autowired
  10. public SubmitOrderStockUpdaterFactory(
  11. GoodsStockDAO goodsStockDAO,
  12. DateProvider dateProvider) {
  13. super(goodsStockDAO, dateProvider);
  14. }
  15. /**
  16. * 获取要更新库存的商品sku id的集合
  17. */
  18. @Override
  19. protected List<Long> getGoodsSkuIds(T parameter) throws Exception {
  20. OrderInfoDTO orderInfoDTO = (OrderInfoDTO) parameter;
  21. List<Long> goodsSkuIds = new ArrayList<Long>();
  22. List<OrderItemDTO> orderItemDTOs = orderInfoDTO.getOrderItems();
  23. for(OrderItemDTO orderItemDTO : orderItemDTOs) {
  24. goodsSkuIds.add(orderItemDTO.getGoodsSkuId());
  25. }
  26. return goodsSkuIds;
  27. }
  28. /**
  29. * 创建商品库存更新组件
  30. * @param goodsStockDOs 商品库存DO对象集合
  31. * @param parameter 订单DTO对象
  32. * @return 商品库存更新组件
  33. */
  34. @Override
  35. protected StockUpdater create(List<GoodsStockDO> goodsStockDOs,
  36. T parameter) throws Exception {
  37. OrderInfoDTO orderInfoDTO = (OrderInfoDTO) parameter;
  38. Map<Long, OrderItemDTO> orderItemDTOMap = new HashMap<Long, OrderItemDTO>(CollectionSize.DEFAULT);
  39. for(OrderItemDTO orderItemDTO : orderInfoDTO.getOrderItems()) {
  40. orderItemDTOMap.put(orderItemDTO.getGoodsSkuId(), orderItemDTO);
  41. }
  42. return new SubmitOrderStockUpdater(goodsStockDOs, goodsStockDAO,
  43. dateProvider, orderItemDTOMap);
  44. }
  45. }
  • 库存中心对订单中心暴露的接口。可以看到,ServiceImpl逻辑变得很清爽。不管两者使用RPC还是消息队列通信,订单中心只需要关心informSubmitOrderEvent这个接口即可,而无需关心库存到底是怎么扣减的。如果后续扣减库存逻辑变化了,只需要在XxxUpdater修改即可,而不用让多个其他中心都修改。
  1. /**
  2. * 通知库存中心,“提交订单”事件发生了
  3. * @param orderDTO 订单DTO
  4. * @return 处理结果
  5. */
  6. @Override
  7. public Boolean informSubmitOrderEvent(OrderInfoDTO orderDTO) {
  8. try {
  9. // 从工厂获取命令直接更新即可
  10. StockUpdater goodsStockUpdateCommand =
  11. submitOrderStockUpdaterFactory.create(orderDTO);
  12. goodsStockUpdateCommand.updateGoodsStock();
  13. } catch (Exception e) {
  14. logger.error("error", e);
  15. return false;
  16. }
  17. return true;
  18. }
  • 同理,当用户支付完,需要支付中心更新库存中心。此时需要的更新操作是:减锁定库存,加已售库存。工厂将支付对象转换为库存对象即可。