0%

设计模式之结构型模式(四):装饰模式

1. 装饰模式概述

  • 概念

    • 在不改变原有类的逻辑的前提下动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活
    • 其别名也可以称为包装器,与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为油漆工模式
  • 特点

    • 增强一个类原有的功能
    • 为一个类添加新的功能
  • 缺点

    • 容易造成程序中共有大量的相似的类
    • 比如 Java I/O 中相似的 InputStream 容易让开发者感到困惑

2. 用于增强功能的装饰模式 Demo

  • 新建颜值接口

    1
    2
    3
    public interface IBeauty {
    int getBeautyValue();
    }
  • 新建 Me 类,实现颜值接口

    1
    2
    3
    4
    5
    6
    7
    public class Me implements IBeauty {

    @Override
    public int getBeautyValue() {
    return 100;
    }
    }
  • 戒指装饰类,将 Me 包装起来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class RingDecorator implements IBeauty {
    private final IBeauty me;

    public RingDecorator(IBeauty me) {
    this.me = me;
    }

    @Override
    public int getBeautyValue() {
    return me.getBeautyValue() + 20;
    }
    }
  • 耳环装饰类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class EarringDecorator implements IBeauty {
    private final IBeauty me;

    public EarringDecorator(IBeauty me) {
    this.me = me;
    }

    @Override
    public int getBeautyValue() {
    return me.getBeautyValue() + 50;
    }
    }
  • 项链装饰类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class NecklaceDecorator implements IBeauty {
    private final IBeauty me;

    public NecklaceDecorator(IBeauty me) {
    this.me = me;
    }

    @Override
    public int getBeautyValue() {
    return me.getBeautyValue() + 80;
    }
    }
  • 客户端测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Client {
    @Test
    public void show() {
    IBeauty me = new Me();
    System.out.println("我原本的颜值:" + me.getBeautyValue());

    // 随意挑选装饰
    IBeauty meWithNecklace = new NecklaceDecorator(me);
    System.out.println("戴上了项链后,我的颜值:" + meWithNecklace.getBeautyValue());

    // 多次装饰
    IBeauty meWithManyDecorators = new NecklaceDecorator(new RingDecorator(new EarringDecorator(me)));
    System.out.println("戴上耳环、戒指、项链后,我的颜值:" + meWithManyDecorators.getBeautyValue());

    // 任意搭配装饰
    IBeauty meWithNecklaceAndRing = new NecklaceDecorator(new RingDecorator(me));
    System.out.println("戴上戒指、项链后,我的颜值:" + meWithNecklaceAndRing.getBeautyValue());
    }
    }
  • 运行程序,输出如下

    1
    2
    3
    4
    我原本的颜值:100
    戴上了项链后,我的颜值:180
    戴上耳环、戒指、项链后,我的颜值:250
    戴上戒指、项链后,我的颜值:200
  • 分析

    • 透明装饰模式:实现了 IBeauty 接口的装饰器仅用于增强功能,并不会改变 Me 原有的功能。由于没有改变接口,也没有新增方法,所以透明装饰模式可以无限装饰
    • 装饰模式是继承的一种替代方案,就增加对象功能来说,装饰模式比生成子类实现更为灵活

3. 用于添加功能的装饰模式 Demo

  • 新建房屋接口

    1
    2
    3
    public interface IHouse {
    void live();
    }
  • 房屋类

    1
    2
    3
    4
    5
    6
    7
    public class House implements IHouse{

    @Override
    public void live() {
    System.out.println("房屋原有的功能:居住功能");
    }
    }
  • 新建粘钩装饰器接口,继承房屋接口

    1
    2
    3
    public interface IStickyHookHouse extends IHouse{
    void hangThings();
    }
  • 粘钩装饰类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class StickyHookDecorator implements IStickyHookHouse {
    private final IHouse house;

    public StickyHookDecorator(IHouse house) {
    this.house = house;
    }

    @Override
    public void live() {
    house.live();
    }

    @Override
    public void hangThings() {
    System.out.println("有了粘钩后,新增了挂东西功能");
    }
    }
  • 客户端测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Client {
    @Test
    public void show() {
    IHouse house = new House();
    house.live();

    IStickyHookHouse stickyHookHouse = new StickyHookDecorator(house);
    stickyHookHouse.live();
    stickyHookHouse.hangThings();
    }
    }
  • 运行程序,输出如下

    1
    2
    3
    房屋原有的功能:居住功能
    房屋原有的功能:居住功能
    有了粘钩后,新增了挂东西功能
  • 分析

    • 半透明装饰模式:没有修改原有的功能,只是扩展了新的功能
    • 半透明的含义:由于新的 IStickyHookHouse 拥有之前 IHouse 不具有的方法,所以如果要使用装饰器中添加的功能,就不得不区别对待装饰前的对象和装饰后的对象。也就是说客户端要使用新方法,必须知道具体的装饰类 StickyHookDecorator,所以这个装饰类对客户端来说是可见的、不透明的;而被装饰者不一定要是 House,它可以是实现了 IHouse 接口的任意对象,所以被装饰者对客户端是不可见的、透明的。由于一半透明、一半不透明,所以称之为半透明装饰模式
    • 半透明装饰模式的局限:多个装饰类不应该存在依赖关系,而应该在原本的类上进行装饰,即半透明装饰模式中,无法多次装饰

4. I/O 中的装饰模式

  • 查看 I/O 源码可知,Java I/O 的设计框架便是使用的装饰者模式

  • InputStream 的继承关系

    InputStream 的继承关系

    • 左边的三个类 FileInputStream、ByteArrayInputStream、ServletInputStream 是 InputStream 的三个子类;右边的三个类 BufferedInputStream、DataInputStream、CheckedInputStream 是三个具体的装饰者类,都为 InputStream 增强了原有功能或添加了新功能
    • BufferedInputStream 没有添加 InputStream 中没有的方法,所以 BufferedInputStream 使用的是透明的装饰模式
    • DataInputStream 中新增了 readInt()readLong() 等方法用于更加方便地读取 int、double 等内容,所以 DataInputStream 使用的是半透明装饰模式
  • OutputStream 的继承关系

    OutputStream 的继承关系

5. 装饰模式和适配器模式的区别

  • 装饰模式不改变原有的接口,仅用于增强原有功能或添加新功能,强调的是锦上添花
  • 纯粹的适配器模式仅用于改变接口,不改变其功能,部分情况下需要改变一点功能以适配新接口。但使用适配器模式时,接口一定会有一个回炉重造的过程
-------------------- 本文结束感谢您的阅读 --------------------