装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
JDK中I/O相关的接口就是基于这种设计模式实现的。需要注意的是I/O中通过FilterInputStream类来实现了InputStream 所有的默认方法,这样其他装饰器类只用实现其需要增强的方法即可,无需简单包裹对 InputStream 对象的函数调用。
例如:首先定义了I/O的基础类 InputStream, 然后FilterInputStream继承了InputStream,然后将InputStream的方法都进行了默认实现,然后BufferedInputStream、DataInputStream都继承FilterInputStream这个装饰器父类。
1. 需求背景
购买商品时经常会用到的限时折扣、红包、抵扣券以及特殊抵扣金等。
例如,每逢双十一,为了加大商城的优惠力度,开发往往要设计红包 + 限时折扣或红包 + 抵扣券等组合来实现多重优惠。而在平时,由于某些特殊原因,商家还会赠送特殊抵扣券给购买用户,而特殊抵扣券 + 各种优惠又是另一种组合方式。
要实现以上这类组合优惠的功能,最快、最普遍的实现方式就是通过大量 if-else 的方式来实现。但这种方式包含了大量的逻辑判断,致使其他开发人员很难读懂业务, 并且一旦有新的优惠策略或者价格组合策略出现,就需要修改代码逻辑,代码扩展性不强,不能满足开闭原则。
2. 实现
下面使用装饰器模式来实现复杂的价格组合,可以灵活的实现各种价格策略的组合。
完整代码git下载地址:装饰器demo
其中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的类即可,满足了对扩展开放,对修改关闭的原则。