zoukankan      html  css  js  c++  java
  • 设计模式总结-装饰者模式

    什么是装饰器模式?

    装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。(抄来的)
    装饰器模式主要有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
    

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

  • 相关阅读:
    EF 错误解决
    TortoiseHg 学习笔记 (转)
    Mysql 命令行 使用 (转)
    2017-9-3 时间字符串格式化(转)
    2017-8-25 c# 获取url参数的五种方法(转)
    alert 的使用方法
    表单关键字查询写法
    Mysql和Mysqli的区别
    php MySQL中 增、删、改、查的写法格式
    一维、二维数组 与 常用的返回数组 以及 fetch_all与fetch_row的区别
  • 原文地址:https://www.cnblogs.com/laoyeye/p/13365380.html
Copyright © 2011-2022 走看看