装饰者设计模式在顾名思义就是在原来逻辑上进行一层装饰逻辑,从而实现不通过if-else实现对优雅的对基础逻辑的扩充。在JDK源码中的InputStream
中就有使用了装饰者的设计模式。从而实现通过BufferedInputStream、DataInputStream等其他修饰InputStream
,增加了缓存读取、类型读取等功能,相当于InputStream之上加了很多修饰功能,在所以它是一个装饰器模式。
这里inputStream
作为一个被装饰的类,然后BufferedInputStream
,对功能加入了带buffer功能的装饰
从构造器也是可以看出来。
构造器初始化的时候需要传入一个inputStream
简单构造一个可以用到装饰器模式的场景。
现在需要对一个订单实际支付金额的计算。
1:计算商品原价。
2:存在优惠券,需要对商品原价扣减。
3:存在红包也需要对空包金额扣减。
这种场景,可以通过if-else,写一大段很长的代码实现,但是给人的感觉不优雅,可拓展性也不好。处理if-else的场景,可以通过策略模式,责任链模式,装饰器模式。
但是这里可能多种优惠策略,不太适合策略模式。责任链模式也是可以的,这里重点说明的是装饰器模式。这里不展开讨论了。
梳理:这里我们可以定义一个计算价格的基类,订单种存在那些优惠,我们可以采用装饰器模式,对原来的类进行装饰。从而实现多种优惠的叠加计算。
定义一个订单对象,一个订单包含多个子单,每个子单包含一个商品,商品绑定价格,以及多个优惠信息。
这里主要关注优惠信息,定义两种类型的优惠:1:打折 ,2:红包
每种优惠类型,都通过属性描述优惠的额度
public class Order { private int id; //订单ID private String orderNo; //订单号 private BigDecimal totalPayMoney; //总支付金额 private List<OrderDetail> list; //详细订单列表 } public class OrderDetail { private int id; //详细订单ID private int orderId;//主订单ID private Merchandise merchandise; //商品详情 private BigDecimal payMoney; //支付单价 } public class Merchandise { private String sku;//商品SKU private String name; //商品名称 private BigDecimal price; //商品单价 private Map<String, SupportPromotions> supportPromotions; //支持促销类型 } public class UserCoupon { private int id; //优惠券ID private int userId; //领取优惠券用户ID private String sku; //商品SKU private BigDecimal coupon; //优惠金额 } public class UserRedPacket { private int id; //红包ID private int userId; //领取用户ID private String sku; //商品SKU private BigDecimal redPacket; //领取红包金额 }
然后定义一个计算订单金额的接口
public interface IBaseCount { BigDecimal countPayMoney(OrderDetail orderDetail); }
分别构建要给计算订单金额的抽象类,无优惠计算类,红包优惠的计算类
public abstract class BaseCountDecorator implements IBaseCount{ private IBaseCount count; public BaseCountDecorator(IBaseCount count) { this.count = count; } public BigDecimal countPayMoney(OrderDetail orderDetail) { BigDecimal payTotalMoney = new BigDecimal(0); if(count!=null) { payTotalMoney = count.countPayMoney(orderDetail); } return payTotalMoney; } }
public class CouponDecorator extends BaseCountDecorator{ public CouponDecorator(IBaseCount count) { super(count); } public BigDecimal countPayMoney(OrderDetail orderDetail) { BigDecimal payTotalMoney = new BigDecimal(0); payTotalMoney = super.countPayMoney(orderDetail); payTotalMoney = countCouponPayMoney(orderDetail); return payTotalMoney; } private BigDecimal countCouponPayMoney(OrderDetail orderDetail) { BigDecimal coupon = orderDetail.getMerchandise().getSupportPromotions().get(PromotionType.COUPON).getUserCoupon().getCoupon(); System.out.println("优惠券金额:" + coupon); orderDetail.setPayMoney(orderDetail.getPayMoney().subtract(coupon)); return orderDetail.getPayMoney(); } }
public class RedPacketDecorator extends BaseCountDecorator{ public RedPacketDecorator(IBaseCount count) { super(count); } public BigDecimal countPayMoney(OrderDetail orderDetail) { BigDecimal payTotalMoney = new BigDecimal(0); payTotalMoney = super.countPayMoney(orderDetail); payTotalMoney = countCouponPayMoney(orderDetail); return payTotalMoney; } private BigDecimal countCouponPayMoney(OrderDetail orderDetail) { BigDecimal redPacket = orderDetail.getMerchandise().getSupportPromotions().get(PromotionType.REDPACKED).getUserRedPacket().getRedPacket(); System.out.println("红包优惠金额:" + redPacket); orderDetail.setPayMoney(orderDetail.getPayMoney().subtract(redPacket)); return orderDetail.getPayMoney(); } }
public class BaseCount implements IBaseCount{ public BigDecimal countPayMoney(OrderDetail orderDetail) { orderDetail.setPayMoney(orderDetail.getMerchandise().getPrice()); System.out.println("商品原单价金额为:" + orderDetail.getPayMoney()); return orderDetail.getPayMoney(); } }
整个计算金额的体系以及构建好了。
然后通过一个计算工厂类将这些计算逻辑连接起来
public class PromotionFactory { public static BigDecimal getPayMoney(OrderDetail orderDetail) { //获取给商品设定的促销类型 Map<String, SupportPromotions> supportPromotionslist = orderDetail.getMerchandise().getSupportPromotions(); //初始化计算类 IBaseCount baseCount = new BaseCount(); if(supportPromotionslist!=null && supportPromotionslist.size()>0) { for(String promotionType: supportPromotionslist.keySet()) {//遍历设置的促销类型,通过装饰器组合促销类型 baseCount = protmotion(supportPromotionslist.get(promotionType), baseCount); } } return baseCount.countPayMoney(orderDetail); } /** * 组合促销类型 * @param supportPromotions * @param baseCount * @return */ private static IBaseCount protmotion(SupportPromotions supportPromotions, IBaseCount baseCount) { if(PromotionType.COUPON.equals(supportPromotions.getPromotionType())) { baseCount = new CouponDecorator(baseCount); }else if(PromotionType.REDPACKED.equals(supportPromotions.getPromotionType())) { baseCount = new RedPacketDecorator(baseCount); } return baseCount; } }
通过工厂类的getPayMoney
方法获取子单商品上所有的的促销类型,然后依次获取最终的装饰对象,执行计算订单金额,最终获取到最终的金额。
public static void main( String[] args ) throws InterruptedException, IOException { Order order = new Order(); init(order); for(OrderDetail orderDetail: order.getList()) { BigDecimal payMoney = PromotionFactory.getPayMoney(orderDetail); orderDetail.setPayMoney(payMoney); System.out.println("最终支付金额:" + orderDetail.getPayMoney()); } }
获得执行结果:
商品原单价金额为:100 红包优惠金额:10 优惠券金额:10 最终支付金额:80
其实这种方式和mybatis的插件的设计模式(责任链+动态代理)很像