设计模式-装饰器模式学习之旅

1,068 阅读6分钟

“这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

一、装饰器模式的定义

装饰器模式(Decorator Pattern),也称为包装模式(Wrapper Pattern)是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。

装饰器模式的核心是功能扩展。使用装饰器模式可以透明且动态地扩展类的功能。

装饰器模式主要用于透明且动态地扩展类的功能。其实现原理为:让装饰器实现被包装类(Concrete Component)相同的接口(Component)(使得装饰器与被扩展类类型一致),并在构造函数中传入该接口(Component)对象,然后就可以在接口需要实现的方法中在被包装类对象的现有功能上添加新功能了。而且由于装饰器与被装饰器类属于同一类型(均为Component),且构造函数的参数为接口类(Component),因此装饰器模式具备嵌套扩展功能,这样我们就能使用装饰器模式一层一层的对最底层被包装类进行功能扩展了。

一句话总结:bigger better stronger !!!越变越强大!!!

说了这么多,看着有些懵哈!没办法,就是这么定义的,官方话。具体慢慢看下边的例子,慢慢体会!!!

C9055A21.gif

二、装饰器模式的应用场景

装饰器模式在我们生活中应用也比较多,如给煎饼加鸡蛋,给蛋糕加上一些水果,给房子装修等,为对象扩展一些额外的职责。装饰器在代码程序中使用于以下场景:

  1. 用于扩展一个类的功能或给一个类添加附加职责。
  2. 动态的给一个对象添加功能,这些功能可以再动态的撤销。
  3. 需要为一批的兄弟类进行改装或加装功能。

来看一个这样的场景,上班族白领其实大多有睡懒觉的习惯,每天早上上班都是踩点,于是很多小伙伴为了多赖一会儿床,都不吃早餐。那么,也有些小伙伴可能在上班路上碰到卖煎饼的路边摊,都会顺带一个到公司茶水间吃早餐。卖煎饼的大姐可以给你的煎饼加鸡蛋,也可以加香肠。

下面我们用代码还原一下码农的生活,首先创建一个煎饼BatterCake类:

public class BatterCake {

    protected String getMsg() {
        return "煎饼";
    }

    public int getPrice() {
        return 5;
    }
}

创建一个加鸡蛋的煎饼BatterCakeWithEgg类:

public class BatterCakeWithEgg extends BatterCake {

    @Override
    protected String getMsg() {
        return super.getMsg() + "+1个鸡蛋";
    }

    @Override
    public int getPrice() {
        //加一个鸡蛋加1块钱
        return super.getPrice() + 1;
    }
}

再创建一个既加鸡蛋又加香肠的BatterCakeWithEggAndSausage类:

public class BatterCakeWithEggAndSausage extends BatterCakeWithEgg {

    @Override
    protected String getMsg() {
        return super.getMsg() + "+1根香肠";
    }

    @Override
    public int getPrice() {
        //加一个香肠加2块钱
        return super.getPrice() + 2;
    }
}

编写客户端测试代码:

public class BatterCakeTest {

    public static void main(String[] args) {
        BatterCake batterCake = new BatterCake();
        System.out.println(batterCake.getMsg() + ",总价格:" + batterCake.getPrice());

        BatterCakeWithEgg batterCakeWithEgg = new BatterCakeWithEgg();
        System.out.println(batterCakeWithEgg.getMsg() + ",总价格:" + batterCakeWithEgg.getPrice());

        BatterCakeWithEggAndSausage batterCakeWithEggAndSausage = new BatterCakeWithEggAndSausage();
        System.out.println(batterCakeWithEggAndSausage.getMsg() + ",总价格:" + batterCakeWithEggAndSausage.getPrice());
    }
}

运行结果:

image.png

运行结果没有问题,但是如果用户需要一个加2个鸡蛋加1根香肠的煎饼,那么用我们现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。如果需求再变,一直加定制显然是不科学的。那么下面我们就用装饰器模式来解决上面的问题。首先创建一个建煎饼的抽象BatterCate类:

public interface BatterCake {

    String getMsg();

    //伪代码就用int类型了
    int getPrice();
}

创建一个基本的煎饼(或者叫基础套餐)BaseBatterCate:

public class BaseBatterCake implements BatterCake {
    @Override
    public String getMsg() {
        return "煎饼";
    }

    @Override
    public int getPrice() {
        return 5;
    }
}

然后,再创建一个扩展套餐的抽象装饰器BatterCakeDecorator类:

public abstract class BatterCakeDecorator implements BatterCake {

    //静态代理,委派
    private BatterCake batterCake;

    public BatterCakeDecorator(BatterCake batterCake) {
        this.batterCake = batterCake;
    }

    protected abstract void doSomething();

    @Override
    public String getMsg() {
        return this.batterCake.getMsg();
    }

    @Override
    public int getPrice() {
        return this.batterCake.getPrice();
    }
}

然后,创建鸡蛋装饰器EggDecorator类:

public class EggDecorator extends BatterCakeDecorator {

    public EggDecorator(BatterCake batterCake) {
        super(batterCake);
    }

    @Override
    protected void doSomething() {

    }

    @Override
    public String getMsg() {
        return super.getMsg() + "+1个鸡蛋";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 1;
    }
}

创建香肠装饰器SausageDecorator类:

public class SausageDecorator extends BatterCakeDecorator {

    public SausageDecorator(BatterCake batterCake) {
        super(batterCake);
    }

    @Override
    protected void doSomething() {

    }

    @Override
    public String getMsg() {
        return super.getMsg() + "+1根香肠";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 2;
    }
}

编写客户端测试代码:

public class BatterCakeTest {

    public static void main(String[] args) {
        BatterCake batterCake;
        //路边摊买一个煎饼
        batterCake = new BaseBatterCake();
        //煎饼有点少,想再加一个鸡蛋
        batterCake = new EggDecorator(batterCake);
        //再加一个鸡蛋
        batterCake = new EggDecorator(batterCake);
        //不行,还是觉得有些少,再加根香肠
        batterCake = new SausageDecorator(batterCake);

        //装饰器更多考虑是扩展
        System.out.println(batterCake.getMsg() + ",总价:" + batterCake.getPrice());
    }
}

运行结果如下:

image.png

一层嵌套一层,每嵌套一层就增强一层。

image.png

此时再回头看,是不是有点眉目了!!!

C9290D88.gif

三、装饰器模式在源码中的应用

装饰器模式在源码中也应用得非常多,在Spring中的TransactionAwareCacheDecorator类,我们也可以来尝试理解一下,这个类主要是用来处理事务缓存的,来看一下代码:

public class TransactionAwareCacheDecorator implements Cache {

  private final Cache targetCache;


  /**
   * Create a new TransactionAwareCache for the given target Cache.
   * @param targetCache the target Cache to decorate
   */
  public TransactionAwareCacheDecorator(Cache targetCache) {
    Assert.notNull(targetCache, "Target Cache must not be null");
    this.targetCache = targetCache;
  }


  /**
   * Return the target Cache that this Cache should delegate to.
   */
  public Cache getTargetCache() {
    return this.targetCache;
  }

再来看一个MVC中的装饰器模式HttpHeadResponseDecorator类:

public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator {


  public HttpHeadResponseDecorator(ServerHttpResponse delegate) {
    super(delegate);
  }

最后,再看看Mybatis中处理缓存的设计org.apache.ibatis.cache.Cache类,找到它的包定位:

image.png

从名字上来看其实更容易理解了,比如FifoCache先入先出算法的缓存,LruCache最近最少使用的缓存,TransactionCache事务相关的缓存,都是采用装饰器模式。

四、装饰器模式和代理模式对比

装饰器模式强调自身功能的扩展,代理模式强调对代理过程的控制。

假设我们按照代理模式进行思考,那么小明只需要找到一个房产中介,让他去干房源搜索的活,联系房东谈价格这些事情,小明只需要等待通知,然后付点中介费就行了。

如果采用装饰器模式进行思考,因为装饰器模式强调的是自身功能扩展,也就是说,如果要找房子,小明自身就要增加房源搜索能力扩展,联系房东谈价格能力扩展,通过相应的装饰器,提升自身能力,一个人做满所有的事情。只有自己强大了,就省了中介(代理)费了!!!

五、装饰器模式的优缺点

优点:

  1. 装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态的给一个对象扩展功能,即插即用。
  2. 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果
  3. 装饰器完全遵守开闭原则。

缺点:

  1. 会出现更多的代码,更多的类,增加程序复杂性。
  2. 动态装饰时,多层装饰时会更复杂。

六、友情链接

设计模式-工厂模式学习之旅

设计模式-单例模式学习之旅

设计模式-原型模式学习之旅

设计模式-建造者模式学习之旅

设计模式-代理模式学习之旅

设计模式-门面模式学习之旅

欢迎大家关注微信公众号(MarkZoe)互相学习、互相交流。