【设计模式实战】-使用装饰器模式优化复杂的商品价格的计算

一 . 装饰器模式简单的介绍

【设计模式实战】-使用装饰器模式优化复杂的商品价格的计算_第1张图片
  
  装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口
  JDK中I/O相关的接口就是基于这种设计模式实现的。需要注意的是I/O中通过FilterInputStream类来实现了InputStream 所有的默认方法,这样其他装饰器类只用实现其需要增强的方法即可,无需简单包裹对 InputStream 对象的函数调用。
  例如:首先定义了I/O的基础类 InputStream, 然后FilterInputStream继承了InputStream,然后将InputStream的方法都进行了默认实现,然后BufferedInputStream、DataInputStream都继承FilterInputStream这个装饰器父类。

二 .优化复杂的商品价格的计算

1. 需求背景

  购买商品时经常会用到的限时折扣、红包、抵扣券以及特殊抵扣金等。
  例如,每逢双十一,为了加大商城的优惠力度,开发往往要设计红包 + 限时折扣或红包 + 抵扣券等组合来实现多重优惠。而在平时,由于某些特殊原因,商家还会赠送特殊抵扣券给购买用户,而特殊抵扣券 + 各种优惠又是另一种组合方式。
  要实现以上这类组合优惠的功能,最快、最普遍的实现方式就是通过大量 if-else 的方式来实现。但这种方式包含了大量的逻辑判断,致使其他开发人员很难读懂业务, 并且一旦有新的优惠策略或者价格组合策略出现,就需要修改代码逻辑,代码扩展性不强,不能满足开闭原则。

2. 实现

  下面使用装饰器模式来实现复杂的价格组合,可以灵活的实现各种价格策略的组合。

完整代码git下载地址:装饰器demo

首先我们看下示例的类的结构。
【设计模式实战】-使用装饰器模式优化复杂的商品价格的计算_第2张图片

其中Order为订单和Merchandise商品的属性类,主订单包含若干详细订单,详细订单中记录了商品信息,商品信息中包含了促销类型信息,一个商品可以包含多个促销类型。

/**
 * 主订单
 * @author tr.wang
 *
 */
public class Order {
	private int id; //订单ID
	private String orderNo; //订单号
	private BigDecimal totalPayMoney; //总支付金额
	private List list; //详细订单列表
}
/**
 * 详细订单
 * @author tr.wang
 *
 */
public class OrderDetail {
	private int id; //详细订单ID
	private int orderId;//主订单ID
	private Merchandise merchandise; //商品详情
	private BigDecimal payMoney; //支付单价
}
/**
 * 商品
 * @author tr.wang
 *
 */
public class Merchandise {
	private String sku;//商品SKU
	private String name; //商品名称
	private BigDecimal price; //商品单价
	private List supportPromotionsList; //支持促销类型
}

/**
 * 促销类型
 * @author tr.wang
 *
 */
public class SupportPromotions implements Cloneable{

	private int id;//该商品促销的ID
	private PromotionType promotionType;//促销类型 1、优惠券 2、红包 3、打折
	private int priority; //优先级
	private UserCoupon userCoupon; //用户领取该商品的优惠券
	private UserRedPacket userRedPacket; //用户领取该商品的红包
	private UserDiscount userDiscount;//用户领取的该商品折扣

	//重写clone方法
    public SupportPromotions clone(){
    	SupportPromotions supportPromotions = null;
        try{
        	supportPromotions = (SupportPromotions)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return supportPromotions;
    }
}

下面是几种优惠类型:

/**
 * 优惠券
 * @author tr.wang
 *
 */
public class UserCoupon {
	
	private int id; //优惠券ID
	private int userId; //领取优惠券用户ID
	private String sku; //商品SKU
	private BigDecimal coupon; //优惠金额

	public UserCoupon(int id, int userId, String sku, BigDecimal coupon) {
		this.id = id;
		this.userId = userId;
		this.sku = sku;
		this.coupon = coupon;
	}
}

/**
 *  @Description 折扣
 *  @Author tr.wang
 *  
 *  */
public class UserDiscount {
  private int id; // 折扣ID
  private int userId; // 领取折扣用户ID
  private String sku; // 商品SKU
  private double  discount; // 折扣,10表示1折

  public UserDiscount(int id, int userId, String sku, double  discount) {
    this.id = id;
    this.userId = userId;
    this.sku = sku;
    this.discount = discount;
  }
}

/**
 * 红包
 * @author tr.wang
 *
 */
public class UserRedPacket {
	private int id; //红包ID
	private int userId; //领取用户ID
	private String sku; //商品SKU
	private BigDecimal redPacket; //领取红包金额

	public UserRedPacket(int id, int userId, String sku, BigDecimal redPacket) {
		this.id = id;
		this.userId = userId;
		this.sku = sku;
		this.redPacket = redPacket;
	}

}

接下来,我们再建立一个计算支付金额的接口类以及基本类:

/**
 * 计算支付金额接口类
 * @author tr.wang
 *
 */
public interface IBaseCount {
	
	BigDecimal countPayMoney(OrderDetail orderDetail);

}
/**
 * 计算支付金额的抽象类
 * @author tr.wang
 *
 */
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;
	}
	

}
/**
 * 支付基本类
 * @author tr.wang
 *
 */
public class BaseCount implements IBaseCount {

	public BigDecimal countPayMoney(OrderDetail orderDetail) {
		orderDetail.setPayMoney(orderDetail.getMerchandise().getPrice());
		System.out.println("商品原单价金额为:" +  orderDetail.getPayMoney());
		
		return orderDetail.getPayMoney();
	}

}

下面是具体的装饰器,继承BaseCountDecorator,然后重写countPayMoney。

/**
 * 计算使用优惠券后的金额
 *
 * @author tr.wang
 */
public class CouponDecorator extends BaseCountDecorator {
  private SupportPromotions supportPromotions;

  public CouponDecorator(IBaseCount count, SupportPromotions supportPromotions) {
    super(count);
    this.supportPromotions = supportPromotions;
  }

  public BigDecimal countPayMoney(OrderDetail orderDetail) {
    super.countPayMoney(orderDetail);
    BigDecimal payTotalMoney = new BigDecimal(0);
    payTotalMoney = countCouponPayMoney(orderDetail);
    return payTotalMoney;
  }

  private BigDecimal countCouponPayMoney(OrderDetail orderDetail) {
    System.out.println("-----------------------使用优惠券-----------------------");
    List supportPromotionsList =
        orderDetail.getMerchandise().getSupportPromotionsList();
    BigDecimal coupon = new BigDecimal(0);
    coupon = supportPromotions.getUserCoupon().getCoupon();
    System.out.println("优惠券金额:" + coupon);

    orderDetail.setPayMoney(
        orderDetail.getPayMoney().subtract(coupon).setScale(2, BigDecimal.ROUND_HALF_UP));
    System.out.println("优惠券后商品价格:" + orderDetail.getPayMoney());
    System.out.println("  ");
    return orderDetail.getPayMoney();
  }
}

/**
 * 计算打折后的金额
 *
 * @author tr.wang
 */
public class DiscountDecorator extends BaseCountDecorator {
  private SupportPromotions supportPromotions;

  public DiscountDecorator(IBaseCount count, SupportPromotions supportPromotions) {
    super(count);
    this.supportPromotions = supportPromotions;
  }

  public BigDecimal countPayMoney(OrderDetail orderDetail) {
    BigDecimal payTotalMoney = new BigDecimal(0);
    super.countPayMoney(orderDetail);
    payTotalMoney = countDiscountPayMoney(orderDetail);
    return payTotalMoney;
  }

  private BigDecimal countDiscountPayMoney(OrderDetail orderDetail) {
    System.out.println("--------------------------打折-------------------------");
    List supportPromotionsList =
        orderDetail.getMerchandise().getSupportPromotionsList();
    double discount = 100;
    discount = supportPromotions.getUserDiscount().getDiscount();

    System.out.println("折扣:" + discount / 100);
    BigDecimal multiply = orderDetail.getPayMoney().multiply(new BigDecimal(discount / 100));
    orderDetail.setPayMoney(multiply.setScale(2, BigDecimal.ROUND_HALF_UP));
    System.out.println("折扣后商品价格:" + orderDetail.getPayMoney());
    System.out.println("  ");
    return orderDetail.getPayMoney();
  }
}

/**
 * 计算使用红包后的金额
 *
 * @author tr.wang
 */
public class RedPacketDecorator extends BaseCountDecorator {
  private SupportPromotions supportPromotions;

  public RedPacketDecorator(IBaseCount count, SupportPromotions supportPromotions) {
    super(count);
    this.supportPromotions = supportPromotions;
  }

  public BigDecimal countPayMoney(OrderDetail orderDetail) {
    BigDecimal payTotalMoney = new BigDecimal(0);
    super.countPayMoney(orderDetail);
    payTotalMoney = countCouponPayMoney(orderDetail);
    return payTotalMoney;
  }

  private BigDecimal countCouponPayMoney(OrderDetail orderDetail) {
    System.out.println("--------------------------使用红包-----------------------");
    BigDecimal redPacket = new BigDecimal(0);
    redPacket = supportPromotions.getUserRedPacket().getRedPacket();
    System.out.println("红包优惠金额:" + redPacket);

    orderDetail.setPayMoney(
        orderDetail.getPayMoney().subtract(redPacket).setScale(2, BigDecimal.ROUND_HALF_UP));
    System.out.println("红包后商品价格:" + orderDetail.getPayMoney());
    System.out.println("  ");
    return orderDetail.getPayMoney();
  }
}

最后,定义一个工厂类来组合各种优惠信息,生成商品的优惠信息。

**
 * 计算促销后的支付价格
 * @author tr.wang
 *
 */
public class PromotionFactory {

  public static BigDecimal getPayMoney(OrderDetail orderDetail) {

    // 获取给商品设定的促销类型
    List supportPromotionslist =
        orderDetail.getMerchandise().getSupportPromotionsList();
    // 初始化计算类
    IBaseCount baseCount = new BaseCount();
    if (supportPromotionslist != null && supportPromotionslist.size() > 0) {
      // 遍历设置的促销类型,通过装饰器组合促销类型
      for (SupportPromotions supportPromotions : supportPromotionslist) {
        baseCount = protmotion(supportPromotions, baseCount);
      }
    }
    return baseCount.countPayMoney(orderDetail);
  }

  /**
   * 组合促销类型
   *
   * @param supportPromotions
   * @param baseCount
   * @return
   */
  private static IBaseCount protmotion(SupportPromotions supportPromotions, IBaseCount baseCount) {
    if (supportPromotions.getPromotionType() == PromotionType.COUPON) {
      baseCount = new CouponDecorator(baseCount, supportPromotions);
    } else if (supportPromotions.getPromotionType() == PromotionType.REDPACKED) {
      baseCount = new RedPacketDecorator(baseCount, supportPromotions);
    } else if (supportPromotions.getPromotionType() == PromotionType.DISCOUNT) {
      baseCount = new DiscountDecorator(baseCount, supportPromotions);
    }
    return baseCount;
  }
}

测试方法

 public static void main(String[] args) throws InterruptedException, IOException {
    Order order = new Order();
    init(order);

    for (OrderDetail orderDetail : order.getList()) {
      System.out.println("   ");
      System.out.println("        【 订单明细Id: "+orderDetail.getId()+"商品名称:"+orderDetail.getMerchandise().getName()+" 】 ");
      System.out.println("   ");

      BigDecimal payMoney = PromotionFactory.getPayMoney(orderDetail);

      System.out.println("-----------------------");
      System.out.println("最终支付金额:" + orderDetail.getPayMoney());
    }
  }

  private static Order init(Order order) {
    List OrderDetailList = getOrderDetails();
    order.setList(OrderDetailList);
    return order;
  }

运行结果:

  【 订单明细Id: 1商品名称:红富士苹果 】 
   
商品原单价金额为:70
--------------------------打折-------------------------
折扣:0.9
折扣后商品价格:63.00
  
-----------------------使用优惠券-----------------------
优惠券金额:3
优惠券后商品价格:60.00
  
--------------------------使用红包-----------------------
红包优惠金额:10
红包后商品价格:50.00
  
--------------------------打折-------------------------
折扣:0.8
折扣后商品价格:40.00
  
-----------------------
最终支付金额:40.00
   
        【 订单明细Id: 2商品名称:8424西瓜 】 
   
商品原单价金额为:50
-----------------------使用优惠券-----------------------
优惠券金额:10
优惠券后商品价格:40.00
  
--------------------------使用红包-----------------------
红包优惠金额:5
红包后商品价格:35.00
  
--------------------------打折-------------------------
折扣:0.75
折扣后商品价格:26.25
  
-----------------------
最终支付金额:26.25

三 总结

  看了上面的代码我们可以看出来每个优惠信息都是单独的一个类,装饰类和被装饰类都只关心自身的业务,不相互干扰,真正实现了解耦。同时还优化了业务代码的结构设计,使得整个业务逻辑清晰、易读易懂。
  此外我们还可以动态的来进行组合各种优惠信息,之后如果新增一个优惠类型也不会对存量的优惠信息影响,只需新实现一个继承了BaseCountDecorator的类即可,满足了对扩展开放,对修改关闭的原则。

你可能感兴趣的:(JAVA,代码实战,设计模式)