基础回顾
- 模板方法是对一系列逻辑的抽象,通常我们会使用一个抽象类,将它子类的通用逻辑抽象到这个抽象类中,并赋予它们具体的实现。同时会抽象出一些抽象逻辑,这些抽象逻辑不赋予具体实现,而是以抽象方法的形式存在于抽象类中。当我们需要这一类的实现类时,直接继承它的抽象类,重载抽象方法即可。这样做的好处是利用了抽象这一面向对象的设计原则,提升代码的可读性和可维护性。
- 在一个系统中,如果多个模块频繁修改另一个模块A的数据,此时可以使用命令模式,将修改的操作抽象为一个个命令。结合模板方法模式,可以将多个命令的共同点抽取出来,减少重复代码。
- 工厂方法模式主要是用来创建复杂对象,可以使用工厂创建一个个的命令。
库存管理案例背景
- 电商中的库存中心很适合使用这几种模式的组合。因为无论是采购入库加库存、下单减库存、退货加库存,都需要对库存中心的各种库存字段进行修改操作。虽然这些修改操作的入参不同,但是都要遵循一定的步骤,例如更新销售库存,更新锁定库存,更新已售数量,更新库存状态等。
- 可以将这些更新库存的操作步骤抽象成一个个命令,然后基于这些命令更新库存,库存中心对外(WMS中心,订单中心,支付中心等)暴露的只是一个个接口,这些中心无需关心库存中心是怎么实现的。
- 如果不使用命令+模板方法,这些更新操作会散落在一个个中心里,如果后续库存中心表进行了修改,这些中心还要关心自己应该如何随着库存中心的变化而变化。
实现
- 定义接口。
StockUpdater是所有命令的顶层接口。StockUpdaterFactory<T>是所有命令工厂的顶层接口。
public interface StockUpdater { /** * 更新商品库存 */ Boolean updateGoodsStock();}public interface StockUpdaterFactory<T> { /** * 创建一个库存更新命令 * @param parameter 参数对象 */ StockUpdater create(T parameter); }
- 定义抽象类。
AbstractStockUpdater是所有命令的抽象类。它同时也是一个模板:规定更新一个商品库存的时候,要考虑它的销售库存、锁定库存、已售库存、库存状态和更新时间。对于不同的更新命令,都可以选择性地设置这几个字段的值,最后再执行更新操作。其中,更新库存状态和修改时间这两步,逻辑是固定的,所以声明为普通方法,其他步骤声明为抽象方法,给各个子类去重载。
public abstract class AbstractStockUpdater implements StockUpdater { private static final Logger logger = LoggerFactory.getLogger( AbstractStockUpdater.class); /** * 商品库存DO对象 */ protected List<GoodsStockDO> goodsStockDOs; /** * 商品库存管理模块的DAD组件 */ protected GoodsStockDAO goodsStockDAO; /** * 日期辅助组件 */ protected DateProvider dateProvider; /** * 构造函数 * @param goodsStockDAO 商品库存管理模块的DAO组件 * @param dateProvider 日期辅助组件 */ public AbstractStockUpdater( List<GoodsStockDO> goodsStockDOs, GoodsStockDAO goodsStockDAO, DateProvider dateProvider) { this.goodsStockDOs = goodsStockDOs; this.goodsStockDAO = goodsStockDAO; this.dateProvider = dateProvider; } /** * 更新商品库存 */ @Override public Boolean updateGoodsStock() { try { updateSaleStockQuantity(); updateLockedStockQuantity(); updateSaledStockQuantity(); updateStockStatus(); updateGmtModified(); executeUpdateGoodsStock(); } catch (Exception e) { logger.error("error", e); } return true; } /** * 更新商品的销售库存 * @throws Exception */ protected abstract void updateSaleStockQuantity() throws Exception; /** * 更新商品的锁定库存 * @throws Exception */ protected abstract void updateLockedStockQuantity() throws Exception; /** * 更新商品的已销售库存 * @throws Exception */ protected abstract void updateSaledStockQuantity() throws Exception; /** * 更新商品的库存状态 */ private void updateStockStatus() throws Exception { for(GoodsStockDO goodsStockDO : goodsStockDOs) { if(goodsStockDO.getSaleStockQuantity() > 0L) { goodsStockDO.setStockStatus(StockStatus.IN_STOCK); } else { goodsStockDO.setStockStatus(StockStatus.NOT_IN_STOCK); } } } /** * 更新商品库存的修改时间 */ private void updateGmtModified() throws Exception { for(GoodsStockDO goodsStockDO : goodsStockDOs) { goodsStockDO.setGmtModified(dateProvider.getCurrentTime()); } } /** * 最后执行的更新商品库存的操作 * @throws Exception */ private void executeUpdateGoodsStock() throws Exception { for(GoodsStockDO goodsStockDO : goodsStockDOs) { goodsStockDAO.updateGoodsStock(goodsStockDO); } }}
- 创建生产命令的工厂
AbstractStockUpdaterFactory。对于订单中心或者WMS中心传入的订单对象或入库对象等参数,库存中心需要将其与自己数据库中的库存对象相对应。这个生产Map的逻辑在子类工厂中的create()方法中处理。
public abstract class AbstractStockUpdaterFactory<T> implements StockUpdaterFactory<T> { private static final Logger logger = LoggerFactory.getLogger( AbstractStockUpdaterFactory.class); /** * 商品库存管理模块的DAO组件 */ protected GoodsStockDAO goodsStockDAO; /** * 日期辅助组件 */ protected DateProvider dateProvider; /** * 构造函数 * @param goodsStockDAO 商品库存管理模块的DAO组件 * @param dateProvider 日期辅助组件 */ public AbstractStockUpdaterFactory( GoodsStockDAO goodsStockDAO, DateProvider dateProvider) { this.goodsStockDAO = goodsStockDAO; this.dateProvider = dateProvider; } /** * 创建库存更新命令 */ @Override public StockUpdater create(T parameter) { try { List<Long> goodsSkuIds = getGoodsSkuIds(parameter); List<GoodsStockDO> goodsStockDOs = createGoodsStockDOs(goodsSkuIds); return create(goodsStockDOs, parameter); } catch (Exception e) { logger.error("error", e); } return null; } /** * 获取商品sku id集合 * @param parameter 参数 * @return 商品sku id集合 * @throws Exception */ protected abstract List<Long> getGoodsSkuIds(T parameter) throws Exception; /** * 创建库存更新命令 * @param parameter 参数 * @param goodsStockDOs 商品库存DO对象集合 * @return 库存更新命令 * @throws Exception */ protected abstract StockUpdater create( List<GoodsStockDO> goodsStockDOs, T parameter) throws Exception; /** * 创建商品库存DO对象集合 * @param goodsSkuIds 商品sku id集合 * @return 商品库存DO对象集合 */ private List<GoodsStockDO> createGoodsStockDOs(List<Long> goodsSkuIds) throws Exception { List<GoodsStockDO> goodsStocks = new ArrayList<GoodsStockDO>(goodsSkuIds.size()); for(Long goodsSkuId : goodsSkuIds) { GoodsStockDO goodsStock = goodsStockDAO.getGoodsStockBySkuId(goodsSkuId); if(goodsStock == null) { goodsStock = createGoodsStock(goodsSkuId); goodsStockDAO.saveGoodsStock(goodsStock); } goodsStocks.add(goodsStock); } return goodsStocks; } /** * 创建商品库存DO对象 * @param goodsSkuId 商品sku id * @return 商品库存DO对象 * @throws Exception */ private GoodsStockDO createGoodsStock(Long goodsSkuId) throws Exception { GoodsStockDO goodsStockDO = new GoodsStockDO(); goodsStockDO.setGoodsSkuId(goodsSkuId); goodsStockDO.setSaleStockQuantity(0L); goodsStockDO.setLockedStockQuantity(0L); goodsStockDO.setSaledStockQuantity(0L); goodsStockDO.setStockStatus(StockStatus.NOT_IN_STOCK); goodsStockDO.setGmtCreate(dateProvider.getCurrentTime()); goodsStockDO.setGmtModified(dateProvider.getCurrentTime()); return goodsStockDO; }}
使用
- 现在订单中心提交订单了,需要库存中心更新库存,具体来说是:减销售库存、增加锁定库存。此时我们创建一个
SubmitOrderStockUpdater,作为库存中心接受订单中心的命令。然后基于这个命令,更新库存。当然,它还需要一个工厂,为它将订单对象转换为自己可以操作的库存对象,并为其注入相关依赖。 - 创建提交订单的命令。
public class SubmitOrderStockUpdater extends AbstractStockUpdater { /** * 订单条目DTO对象集合 */ private Map<Long, OrderItemDTO> orderItemDTOMap; /** * 构造函数 * @param goodsStockDOs 商品库存DO对象集合 * @param goodsStockDAO 商品库存管理模块DAO组件 * @param dateProvider 日期辅助组件 */ public SubmitOrderStockUpdater( List<GoodsStockDO> goodsStockDOs, GoodsStockDAO goodsStockDAO, DateProvider dateProvider, Map<Long, OrderItemDTO> orderItemDTOMap) { super(goodsStockDOs, goodsStockDAO, dateProvider); this.orderItemDTOMap = orderItemDTOMap; } /** * 更新销售库存 */ @Override protected void updateSaleStockQuantity() throws Exception { for(GoodsStockDO goodsStockDO : goodsStockDOs) { OrderItemDTO orderItemDTO = orderItemDTOMap.get(goodsStockDO.getGoodsSkuId()); goodsStockDO.setSaleStockQuantity(goodsStockDO.getSaleStockQuantity() - orderItemDTO.getPurchaseQuantity()); } } /** * 更新锁定库存 */ @Override protected void updateLockedStockQuantity() throws Exception { for(GoodsStockDO goodsStockDO : goodsStockDOs) { OrderItemDTO orderItemDTO = orderItemDTOMap.get(goodsStockDO.getGoodsSkuId()); goodsStockDO.setLockedStockQuantity(goodsStockDO.getLockedStockQuantity() + orderItemDTO.getPurchaseQuantity()); } } /** * 更新已销售库存 */ @Override protected void updateSaledStockQuantity() throws Exception { }}
- 创建工厂
SubmitOrderStockUpdaterFactory<T>。
@Componentpublic class SubmitOrderStockUpdaterFactory<T> extends AbstractStockUpdaterFactory<T> { /** * 构造函数 * @param goodsStockDAO 商品库存管理模块DAO组件 * @param dateProvider 日期辅助组件 */ @Autowired public SubmitOrderStockUpdaterFactory( GoodsStockDAO goodsStockDAO, DateProvider dateProvider) { super(goodsStockDAO, dateProvider); } /** * 获取要更新库存的商品sku id的集合 */ @Override protected List<Long> getGoodsSkuIds(T parameter) throws Exception { OrderInfoDTO orderInfoDTO = (OrderInfoDTO) parameter; List<Long> goodsSkuIds = new ArrayList<Long>(); List<OrderItemDTO> orderItemDTOs = orderInfoDTO.getOrderItems(); for(OrderItemDTO orderItemDTO : orderItemDTOs) { goodsSkuIds.add(orderItemDTO.getGoodsSkuId()); } return goodsSkuIds; } /** * 创建商品库存更新组件 * @param goodsStockDOs 商品库存DO对象集合 * @param parameter 订单DTO对象 * @return 商品库存更新组件 */ @Override protected StockUpdater create(List<GoodsStockDO> goodsStockDOs, T parameter) throws Exception { OrderInfoDTO orderInfoDTO = (OrderInfoDTO) parameter; Map<Long, OrderItemDTO> orderItemDTOMap = new HashMap<Long, OrderItemDTO>(CollectionSize.DEFAULT); for(OrderItemDTO orderItemDTO : orderInfoDTO.getOrderItems()) { orderItemDTOMap.put(orderItemDTO.getGoodsSkuId(), orderItemDTO); } return new SubmitOrderStockUpdater(goodsStockDOs, goodsStockDAO, dateProvider, orderItemDTOMap); } }
- 库存中心对订单中心暴露的接口。可以看到,
ServiceImpl逻辑变得很清爽。不管两者使用RPC还是消息队列通信,订单中心只需要关心informSubmitOrderEvent这个接口即可,而无需关心库存到底是怎么扣减的。如果后续扣减库存逻辑变化了,只需要在XxxUpdater修改即可,而不用让多个其他中心都修改。
/** * 通知库存中心,“提交订单”事件发生了 * @param orderDTO 订单DTO * @return 处理结果 */ @Override public Boolean informSubmitOrderEvent(OrderInfoDTO orderDTO) { try { // 从工厂获取命令直接更新即可 StockUpdater goodsStockUpdateCommand = submitOrderStockUpdaterFactory.create(orderDTO); goodsStockUpdateCommand.updateGoodsStock(); } catch (Exception e) { logger.error("error", e); return false; } return true; }
- 同理,当用户支付完,需要支付中心更新库存中心。此时需要的更新操作是:减锁定库存,加已售库存。工厂将支付对象转换为库存对象即可。