裝飾者設計模式在業務中的實踐
裝飾者設計模式在顧名思義就是在原來邏輯上進行一層裝飾邏輯,從而實現不通過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的外掛的設計模式(責任鏈+動態代理)很像