装饰者设计模式在业务中的实践
装饰者设计模式在顾名思义就是在原来逻辑上进行一层装饰逻辑,从而实现不通过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
方法获取子单商品上所有的的促销类型,然后依次获取最终的装饰对象,执行计算订单金额,最终获取到最终的金额。
DEMO
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的插件的设计模式(责任链+动态代理)很像