设计模式总结-装饰者模式

什么是装饰器模式?

装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。(抄来的)
装饰器模式主要有4个角色:
抽象接口:抽离出最基本的需求。
比如小卖铺家的房子马上要交付了,装修就是我最基本的需求
具体对象:最基本的需求实现。
假设我老婆是个设计师,室内设计就是我自己做的基本实现。
基本装饰角色:抽象类,接收请求,将请求转发给具体的装饰角色。
我去找了个包工头来按照我的设计图做装修的工作
具体装饰角色:负责具体功能的装饰活动。
包工头自己不做事,找了一堆小工来干活,有装水电的、有装窗户的、有铺地板的

本质来讲干活的小工和我来说没有任何关系,我可以自由的选择不同的人来干活,这就是装饰器模式。

装饰器模式的优点

如果我不找包工头装修公司来帮我装修,我会找家里的叔叔阿姨来帮我干活,想想我都头大。
找了装修公司后是不是一下省了好多活,思路也更清洗了。
同样,装饰器模式也有类似的优点

  1. 被装饰类(具体对象)和装饰类(具体装修角色)没有耦合关系,可以各自独立发展。
  2. 代码结构更加清晰,有利于后续的项目维护
  3. 动态扩展对象功能,比继承更加灵活,可以对一个对象作出不同的修饰组合,满足复杂的功能需求

案例的简单实现

以上文中的装修案例为例,我们实现以下代码。
1、首先是基本需求的接口:

/**
 * 基本需求
 * @author laoyeye.net
 *
 */
public interface IDecorator {
	
	/**
	 * 装修方法
	 */
	void decorate();

}

2、然后是需求最基本的实现
我自己完成的室内设计图纸

/**
 * 具体对象基本实现
 * @author laoyeye.net
 *
 */
public class Decorator implements IDecorator{


	@Override
	public void decorate() {
		System.out.println("老婆画好了装修设计图");
	}
}

3、接着到接口,基本装饰角色,也就是包工头了

/**
 * 基本装饰类
 * @author laoyeye.net
 *
 */
public abstract class BaseDecorator implements IDecorator{

	private IDecorator decorator;
	
	public BaseDecorator(IDecorator decorator) {
		this.decorator = decorator;
	}
	
	/**
	 * 调用装饰方法
	 */
	@Override
	public void decorate() {
		if(decorator != null) {
			decorator.decorate();
		}
	}
}

4、最后是具体装饰角色,实现对基本功能的扩展

/**
 * 窗户装饰者
 * @author laoyeye.net
 *
 */
public class WindowDecorator extends BaseDecorator{

	public WindowDecorator(IDecorator decorator) {
		super(decorator);
	}
	
	/**
	 * 窗帘具体装饰方法
	 */
	@Override
	public void decorate() {
		super.decorate();
		System.out.println("窗户小工,开始装饰窗户");
	}

}

/**
 * 家具装饰类
 * @author laoyeye.net
 *
 */
public class FurnitureDecorator extends BaseDecorator{

	public FurnitureDecorator(IDecorator decorator) {
		super(decorator);
	}
	
	/**
	 * 家具装饰方法
	 */
	@Override
	public void decorate() {
		super.decorate();
		System.out.println("家具小工, 开始装饰家具");
	}

}

5、最后我们来运行下上述代码,看下效果:

public class App {

	public static void main(String[] args) {
		IDecorator decorator = new Decorator();
		IDecorator windowDecorator = new WindowDecorator(decorator);
		IDecorator waterDecorator = new FurnitureDecorator(windowDecorator);
		waterDecorator.decorate();
	}
}

效果如下:

老婆画好了装修设计图
窗户小工,开始装饰窗户
家具小工, 开始装饰家具

这样就基本实现了装饰者模式,但是相信很多同学有我刚开始学设计模式时的疑惑,这段代码我是看懂了,但是我怎么应用呢?我项目上啥时候可以用到呢。
这里我们再举个具体项目上的案例,相信看了项目案例后大家心里都会有个概念了。

互联网金融投资真实案例

首先在互联网金融中,很多时候用户会选择投资一个线上的标的。同时为了吸引用户,商家也会给用户发很多种类型的现金券、红包、加息券之类的。
这里我们就简单实现,不做过多限制。

假设一个用户要买一个线上标的5000份,每份的金额为1元,正常情况下,客户应该支付5000元。但是因为我们给客户发了优惠券和红包之类的,实际用户支付的钱并没有那么多。如何设计一个架构简洁,高可维护的系统呢,这时候我们就可以用到本文所讲的装饰者模式。

先介绍几个实体类:
1、订单类
客户实际下的订单信息:

/**
 * 订单类
 * @author laoyeye.net
 *
 */
public class Order {

	/**
	 * 订单id
	 */
	private int id;
	/**
	 * 标的信息
	 */
	private FundProduct fundProduct;
	/**
	 * 投资份额
	 */
	private Integer investCount;

	private Integer investAmount;

	/**
	 * 优惠券信息
	 */
	private MarketCouponInfo couponInfo;
	/**
	 * 红包信息
	 */
	private MarketCouponInfo redPacketInfo;
}

2、营销券信息类:

/**
 *	营销券信息
 * @author laoyeye.net
 *
 */
public class MarketCouponInfo implements Cloneable{

	//券ID
	private int id;
	//券类型
	private CouponTypeEnum couponType;
	//券金额
	private Integer couponAmount;

	@Override
	public MarketCouponInfo clone(){
		MarketCouponInfo couponInfo = null;
		try {
			couponInfo = (MarketCouponInfo)super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}

		return couponInfo;
	}
}

3、产品信息类

/**
 *	产品信息
 * @author laoyeye.net
 *
 */
public class FundProduct {

	/**
	 * 产品code
	 */
	private String productCode;
	/**
	 * 标的名称
	 */
	private String name;

	/**
	 * 标的单价
	 */
	private Integer price;

	/**
	 * 支持的营销券类型
	 */
	private List<CouponTypeEnum> couponTypeList;
}

4、支持的券枚举

/**
 * <p>
 * 优惠券类型
 * </p>
 */

public enum CouponTypeEnum {
    RED_PACKET("红包"),
    COUPON("优惠券");

    private String desc;

    CouponTypeEnum(String name) {
        this.desc = name;
    }

    public String getDesc() {
        return desc;
    }

}

5、抽离出的基本需求
从第五步开始就真正进入了装饰模式的流程。
咱们这个需求最基本的需要就是计算客户实际金额,自然抽离出的基本需求是下面这个样子

/**
 * 基本需求,计算投资金额
 * @author laoyeye.net
 *
 */
public interface IInvestDecorator {
	
	Integer calculateInvestAmount(Order order);

}

6、具体对象,最基本的实现

/**
 * 原始投资金额计算
 * @author laoyeye.net
 *
 */
public class BaseInvest implements IInvestDecorator {

	@Override
	public Integer calculateInvestAmount(Order order) {

		System.out.println("订单总金额为:" +  order.getInvestCount() * order.getFundProduct().getPrice());

		return order.getInvestCount() * order.getFundProduct().getPrice();
	}
}

7、基本装饰角色(类似于上文中的包工头)

/**
 * 投资金额计算的装饰类
 * @author laoyeye.net
 *
 */
public abstract class BaseInvestDecorator implements IInvestDecorator{

	private IInvestDecorator investDecorator;

	public BaseInvestDecorator(IInvestDecorator investDecorator) {
		this.investDecorator = investDecorator;
	}


	@Override
	public Integer calculateInvestAmount(Order order) {
		Integer payAmount = 0;

		if(investDecorator != null) {
			payAmount = investDecorator.calculateInvestAmount(order);
		}
		return payAmount;
	}
}

8、接下来是两个具体实现角色
红包修饰类:

/**
 * 计算红包后的金额
 * @author laoyeye.net
 *
 */
public class RedPacketDecorator extends BaseInvestDecorator{

	public RedPacketDecorator(IInvestDecorator investDecorator) {
		super(investDecorator);
	}

	@Override
	public Integer calculateInvestAmount(Order order) {
		order.setInvestAmount(super.calculateInvestAmount(order));
		return calculateCouponAmount(order);
	}

	private Integer calculateCouponAmount(Order order) {
		System.out.println("红包优惠金额:" + order.getRedPacketInfo().getCouponAmount());
		return order.getInvestAmount() - order.getRedPacketInfo().getCouponAmount();
	}
}

优惠券修饰类:

/**
 * 计算优惠券后的金额
 * @author laoyeye.net
 *
 */
public class CouponDecorator extends BaseInvestDecorator{

	public CouponDecorator(IInvestDecorator investDecorator) {
		super(investDecorator);
	}

	@Override
	public Integer calculateInvestAmount(Order order) {
		order.setInvestAmount(super.calculateInvestAmount(order));
		return calculateCouponAmount(order);
	}

	private Integer calculateCouponAmount(Order order) {
		System.out.println("优惠券金额:" + order.getCouponInfo().getCouponAmount());
		return order.getInvestAmount() - order.getCouponInfo().getCouponAmount();
	}
}

9、工厂类
这是和上面装修的案例有稍许的不同,如果按照上述的方法来写的话,每次增加新的需求都要改动方法,这并不是工业级代码所想要的。
所以这里我们增加一个工厂类,当增加一个新的装饰需求,我们只需要修改工厂类,而不必动实际的代码即可。当然这部分实现方式还有很多,最核心的原则就是增加新的需求时,不需要频繁的改动核心代码。

/**
 * 营销活动工厂类
 * @author laoyeye.net
 *
 */
public class MarketFactory {
	
	public static Integer getPayAmount(Order order) {
		
		//获取标的支持的营销类型
		List<CouponTypeEnum> couponTypeList = order.getFundProduct().getCouponTypeList();

		//初始化基本需求实现
		IInvestDecorator decorator = new BaseInvest();
		if(couponTypeList !=null && couponTypeList.size()>0) {
			for (CouponTypeEnum couponTypeEnum : couponTypeList) {
				decorator = decoratorType(couponTypeEnum, decorator);
			}
		}
		return decorator.calculateInvestAmount(order);
	}
	
	/**
	 * 修饰方法的各种组合
	 * @param couponType
	 * @param investDecorator
	 * @return
	 */
	private static IInvestDecorator decoratorType(CouponTypeEnum couponType, IInvestDecorator investDecorator) {
		if(CouponTypeEnum.COUPON.equals(couponType)) {
			investDecorator = new CouponDecorator(investDecorator);
		}else if(CouponTypeEnum.RED_PACKET.equals(couponType)) {
			investDecorator = new RedPacketDecorator(investDecorator);
		}
		return investDecorator;
	}

}

10、同样最后我们来运行下效果看下:

public class App {

    public static void main(String[] args) {
        //基金投资产品
        FundProduct product = new FundProduct();
        product.setProductCode("NFSY");
        product.setName("南方石油");
        product.setPrice(1);
        product.setCouponTypeList(Arrays.asList(CouponTypeEnum.COUPON, CouponTypeEnum.RED_PACKET));

        MarketCouponInfo info = new MarketCouponInfo();
        info.setId(1);
        info.setCouponType(CouponTypeEnum.RED_PACKET);
        info.setCouponAmount(50);

        MarketCouponInfo clone = info.clone();
        clone.setId(2);
        clone.setCouponType(CouponTypeEnum.COUPON);
        clone.setCouponAmount(100);
        //用户下单
        Order order = new Order();
        order.setId(1);
        //买500份,查询支付金额
        order.setInvestCount(5000);
        order.setFundProduct(product);
        //券信息
        order.setCouponInfo(clone);
        order.setRedPacketInfo(info);
        
        Integer payAmount = MarketFactory.getPayAmount(order);
        System.out.println("实际应该支付金额:" + payAmount);
    }
}

最终实现效果:

订单总金额为:5000
优惠券金额:100
红包优惠金额:50
实际应该支付金额:4850

到这里相信大家对装饰者模式的应用场景有了一定的了解,装饰者模式的应用让我们抽离出基本需求和扩展需求,更灵活的对扩展需求进行组合。

posted @ 2020-07-23 11:49  小卖铺的老爷爷  阅读(558)  评论(0编辑  收藏  举报


^
TOP