裝飾者設計模式在業務中的實踐

一懶眾衫小QAQ發表於2022-01-16

裝飾者設計模式在業務中的實踐

​ 裝飾者設計模式在顧名思義就是在原來邏輯上進行一層裝飾邏輯,從而實現不通過if-else實現對優雅的對基礎邏輯的擴充。在JDK原始碼中的InputStream中就有使用了裝飾者的設計模式。從而實現通過BufferedInputStream、DataInputStream等其他修飾InputStream,增加了快取讀取、型別讀取等功能,相當於InputStream之上加了很多修飾功能,在所以它是一個裝飾器模式。

image-20220113001347847

這裡inputStream作為一個被裝飾的類,然後BufferedInputStream,對功能加入了帶buffer功能的裝飾

從構造器也是可以看出來。

image-20220116181241019

構造器初始化的時候需要傳入一個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的外掛的設計模式(責任鏈+動態代理)很像

相關文章