设计模式总结-装饰者模式
什么是装饰器模式?
装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。(抄来的)
装饰器模式主要有4个角色:
抽象接口:抽离出最基本的需求。
比如小卖铺家的房子马上要交付了,装修就是我最基本的需求
具体对象:最基本的需求实现。
假设我老婆是个设计师,室内设计就是我自己做的基本实现。
基本装饰角色:抽象类,接收请求,将请求转发给具体的装饰角色。
我去找了个包工头来按照我的设计图做装修的工作
具体装饰角色:负责具体功能的装饰活动。
包工头自己不做事,找了一堆小工来干活,有装水电的、有装窗户的、有铺地板的
本质来讲干活的小工和我来说没有任何关系,我可以自由的选择不同的人来干活,这就是装饰器模式。
装饰器模式的优点
如果我不找包工头装修公司来帮我装修,我会找家里的叔叔阿姨来帮我干活,想想我都头大。
找了装修公司后是不是一下省了好多活,思路也更清洗了。
同样,装饰器模式也有类似的优点
- 被装饰类(具体对象)和装饰类(具体装修角色)没有耦合关系,可以各自独立发展。
- 代码结构更加清晰,有利于后续的项目维护
- 动态扩展对象功能,比继承更加灵活,可以对一个对象作出不同的修饰组合,满足复杂的功能需求
案例的简单实现
以上文中的装修案例为例,我们实现以下代码。
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
到这里相信大家对装饰者模式的应用场景有了一定的了解,装饰者模式的应用让我们抽离出基本需求和扩展需求,更灵活的对扩展需求进行组合。
❤本博客只适用于研究学习为目的,大多为学习笔记,如有错误欢迎指正,如有误导敬请谅解(本人尽力保证90%的验证和10%的猜想)。
❤如果这篇文章对你有一点点的帮助请给一份推荐! 谢谢!你们的鼓励是我继续前进的动力。