装饰模式(Decorator)示例详解

模式概述

  装饰器模式在java中很常见,其中心的本质思想就是动态组合,这种组合很精妙的实现了生活中类似于定制化的功能,可以按照客户的要求来满足客户的定制场景,同时又让编码变得没有那么的复杂,学好了装饰模式,能真正的理解面向对象编程的 本质,也会让人有一种豁然开朗的感觉。
  java中常用的装饰模式的地方就是我们熟知的IO流,InputStream系列和OutputStream系列都是状态模式的设计体现。有了这种模式,我们就可以很方便的将一个对象选择写入到一个file中还是写成内存byte中,使用方可以根据自己的需求定制,而且不用再根据业务场景增加多余的类;

模式结构uml和类介绍

  • 装饰器模式的uml结构图如下所示:
    装饰模式(Decorator)示例详解_第1张图片

  • 每个类的功能如下:

    • Component : 组件对象的接口,可以给这些对象动态的添加职责
    • ConcreteComponent :具体的对象,实现组件对象的接口,通常是装饰器的原始对象,后面的装饰器就是要给这些对象添加职责。
    • Decorator : 所有装饰器对象的父类,需要和组件接口一致的接口,并且持有一个组件对象
    • ConcreteDecorator :装饰器对象的具体实现,实现具体要添加的功能。
  • 实现思路
      装饰器模式在具体的实现中,首先要有一个组件对象,这是实现装饰器对象的基础,这个可以是一个接口,也可以是一个抽象类,其次,我们需要实现一个或者多个组件的实现类,座位被装饰的原始对象,原始对象的意思就是没有装饰之前的对象,比如IO中的file和byte两个流,这两个流就是同一个级别的原始对象。然后才是我们的重点,装饰器的设计:首先我们要实现一个抽象的Decorator对象,这个对象首先需要实现或者继承原来的装饰组件(Component),因为我们就是为了装饰那个组件的实现类,当然要有相同的方法;其次就是需要持有一个组件对象,这是因为我们完成了装饰逻辑以后,还是需要被装饰的对象去执行功能。抽象的装饰对象(Decorator)还是很有必要的,虽然我们可以直接实现装饰器的实现类,但是那样就需要在每个实现类中注入每个组件对象,没法统一规范,写这样一个对象,就可以统一规范注入什么样的组件对象,也规范了和组件对象相同的方法。
      在具体的使用中,我们先创建被装饰的原始对象(ConcreteComponent),然后创建具体的装饰对象(ConcreteDecorator),将组件对象注入到装饰对象中,然后调用装饰对象的方法,就可以完成装饰工作。

使用场景示例

  我们用促销的例子来举例,来实现一个具体的促销场景。为了演示装饰模式的扩展性,首先我们先假设该促销目前有两个活动,一个是团购活动,一个是节假日促销活动。实现思路和代码如下:

  1. 我们需要先实现一个抽象类AbstractSalePromotion,用来定义本次活动的顶层方法
/**
 * 促销活动的抽象类
 */
public abstract class AbstractSalePromotion {

    /**
     * 获得活动价格
     *
     * @return :
     */
    abstract double getPromotionPrice();
}
  1. 我们再实现一个没有活动的实现类,作为被装饰的原始对象
/**
 * 原始产品
 */
public class OriginalProduct extends AbstractSalePromotion {
    @Override
    double getPromotionPrice() {
        // 原始产品,没有折扣
        return 100;
    }
}
  1. 定义一个抽象的装饰器类,作为装饰器的父类,这个类需要继承AbstractSalePromotion类并拥有一个AbstractSalePromotion的对象
/**
 * 装饰器的抽象类
 */
public abstract class AbstractDecorator extends AbstractSalePromotion {

    private AbstractSalePromotion salePromotion;

    public AbstractDecorator(AbstractSalePromotion salePromotion) {
        this.salePromotion = salePromotion;
    }

    @Override
    double getPromotionPrice() {
        return salePromotion.getPromotionPrice();
    }
}
  1. 分别实现两个装饰器类的具体实现类,一个是团购的,一个是节假日的装饰类
/**
 * 团购活动价
 */
public class GroupSalePromotion extends AbstractDecorator{
    public GroupSalePromotion(AbstractSalePromotion salePromotion) {
        super(salePromotion);
    }

    @Override
    double getPromotionPrice() {
        double promotionPrice = super.getPromotionPrice();
        System.out.println("团队折扣:打九折!!!");
        return promotionPrice * 0.9;
    }
}
/**
 * 节假日折扣
 */
public class HolidaySalePromotion extends AbstractDecorator{
    public HolidaySalePromotion(AbstractSalePromotion salePromotion) {
        super(salePromotion);
    }

    @Override
    double getPromotionPrice() {
        double promotionPrice = super.getPromotionPrice();
        System.out.println("节假日折扣:打八折!!!");
        return promotionPrice * 0.8;
    }
}
  1. 我们来写个测试类
public class Client {

    /**
     * 只有团队折扣
     */
    @Test
    public void testGroupSalePromotion() {
        AbstractSalePromotion salePromotion = new GroupSalePromotion(new OriginalProduct());
        System.out.println("最终价格:" + salePromotion.getPromotionPrice());
    }
 }

执行结果:

-------------------------------------------------------------
团队折扣:打九折!!!
最终价格:90.0
  1. 为什么说装饰器的本质是组合实现呢?我们在使用中就可以看出,目前有两个装饰器类,一个是团购的装饰器,一个是节假日的装饰器,这个时候,如果遇上一个顾客,既有团购,又是在节假日过来,我们就可以动态的实现一个团购,一个节假日的场景,用来满足顾客的要求,测试代码和结果如下:
    /**
     * 两个团队折扣
     */
    @Test
    public void testDoubleGroupSalePromotion() {
        System.out.println("-------------------------------------------------------------");
        AbstractSalePromotion salePromotion = new GroupSalePromotion(new GroupSalePromotion(new OriginalProduct()));
        System.out.println("最终价格:" + salePromotion.getPromotionPrice());
    }
-------------------------------------------------------------
团队折扣:打九折!!!
节假日折扣:打八折!!!
最终价格:72.0

要是有个极端的场景,一个客户拿到了两个团购的折扣,我们也可以轻松的实现,创建两个团购装饰类就行,而不是需要新建一个两个团购的装饰类,这就达到了根据客户动态定制的逻辑,测试代码和结果如下:

    /**
     * 两个团队折扣
     */
    @Test
    public void testDoubleGroupSalePromotion() {
        System.out.println("-------------------------------------------------------------");
        AbstractSalePromotion salePromotion = new GroupSalePromotion(new GroupSalePromotion(new OriginalProduct()));
        System.out.println("最终价格:" + salePromotion.getPromotionPrice());
    }
-------------------------------------------------------------
团队折扣:打九折!!!
团队折扣:打九折!!!
最终价格:81.0
  1. 为什么说装饰器模式有很好的扩展性呢?我们假设这个商场今年是特殊的一年:十周年店庆,十周年这一天,我们要加大力度,这个时候我们只需要实现一个十周年店庆的装饰实现类即可,这个时候就有了三个具体的装饰类,平时根据不同的优惠政策给客户定制化优惠就可以。十周年店庆的装饰类如下:
/**
 * 十周年庆折扣
 */
public class TenYearSalePromotion extends AbstractDecorator{
    public TenYearSalePromotion(AbstractSalePromotion salePromotion) {
        super(salePromotion);
    }

    @Override
    double getPromotionPrice() {
        double promotionPrice = super.getPromotionPrice();
        System.out.println("十周年折扣:打五折!!!");
        return promotionPrice * 0.5;
    }
}

如果一个客户既是团购,又是在节假日,还在店庆那天来,而商场又满足优惠可以同时享受,我们也不用怕,只需要三个具体的装饰类相互包装,就能实现具体的逻辑,测试代码和结果如下:

    /**
     * 团队、节假日、十周年折扣
     */
    @Test
    public void testTenYearGroupAndHolidaySalePromotion() {
        System.out.println("-------------------------------------------------------------");
        AbstractSalePromotion salePromotion = new TenYearSalePromotion(
            new HolidaySalePromotion(new GroupSalePromotion(new OriginalProduct())));
        System.out.println("最终价格:" + salePromotion.getPromotionPrice());
    }
-------------------------------------------------------------
团队折扣:打九折!!!
节假日折扣:打八折!!!
十周年折扣:打五折!!!
最终价格:36.0
  1. 促销活动示例的uml类图如下:

装饰模式(Decorator)示例详解_第2张图片

总结

  • 装饰模式的本质是动态组合,这种动态组合是为了让调用更加简单,给调用端做了一个定制化处理,不需要被调用端为了每种业务场景去实现不同的功能,只需要不断的组合就可以。
  • 装饰模式、策略模式、组合模式、静态代理模式好像都是会持有一个对象,然后对对象进行加强或者其他的操作,好像很难分清楚,在这里我做一个我个人的理解:
    • 首先,设计模式是一个思想层面的东西,本质是为了解决某一种特性场景的问题而生的,所以其区别就不是行为或者结构上面的区别,二是不同的思想层面的区别,也就是侧重点不一样,本质上来说,这四种实现结构是很像的,但是侧重点不一样。
    • 装饰模式侧重于调用端的动态组合,是一种定制化的场景而生的,其一定会要求有原始对象,然后再依照这个实现自己的逻辑。
    • 策略模式是为了执行不同的实现的时候能够用不同的策略来生的,其传入进去的时候是一个实现的策略对象,而且,策略模式一般是在方法调用的时候传入策略对象,装饰模式是构造的时候就传入组件对象。
    • 组合模式更像是一种结构上的实现方法,只要能把对象传入,然后反向的调用到组合的对象就算,这样的话,这个更像是具体的实现的方式。
    • 静态代理和装饰模式比较像,都是构造方法传入,实现了被代理的接口,但是代理强调的是控制被代理类或者增强被代理类,其目标是加强实现的逻辑,而装饰的本质是方便调用端的组合实现,虽然在实现上一样,但是在方向上有区别。

后记
  个人总结,欢迎转载、评论、批评指正

你可能感兴趣的:(设计模式,设计模式,java,装饰器模式)