结构篇-装饰器模式


装饰指在某物件上装点额外饰品的行为,以使其原本朴素的外表变得更加饱满、华丽,而装饰器(装饰者)就是能够化“腐朽”为神奇的利器。装饰器模式(Decorator)能够在运行时动态地为原始对象增加一些额外的功能,使其变得更加强大。从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能,区别在于方式的不同,后者是在编译时(compile-time)静态地通过对原始类的继承完成,而前者则是在程序运行时(run-time)通过对原始对象动态地“包装”完成,是对类实例(对象)“装饰”的结果。


提示:以下是本篇文章正文内容,下面案例可供参考

一、从素面朝天到花容月貌

室内装修对房屋视觉效果的改善立竿见影,人们化妆也是如此,“人靠衣装马靠鞍”,人们总是惊叹女生们魔法师一般的化妆技巧,可以从素面朝天变成花容月貌,化妆前后简直判若两人,这正是装饰器的粉饰效果在发挥作用。

1.展示行为

化妆的过程也许对软件研发人员来说比较陌生,但我们可以从设计模式的角度出发,对这项充满神秘色彩的工作进行拆解和分析。下面开始我们的代码实战,首先对于任何妆容展示者必然对应一个标准的展示行为show(),我们将它抽象出来定义为接口Showable。

public interface Showable {   
    //标准展示行为
    public void show();
}

2.女生类

Showable这个标准行为需要人去实现,女生们绝对当仁不让,下面来定义女生类。

public class Girl implements Showable{
    @Override
    public void show() {
        System.out.print("女生的素颜");
    }
}

3.装饰器

女生类中实现了其展示行为,因为目前还没有任何化妆效果,所以展示的只是女生的素颜。如果客户端直接调用show()方法,就会出现素面朝天的结果,这样就达不到我们要的妆容效果了。所以重点来了,此刻我们得借助“化妆品”这种工具来开始这场化妆仪式。

public class Decorator implements Showable{
    //被装修的展示者
    Showable showable;

    public Decorator(Showable showable) {
        this.showable = showable;
    }

    @Override
    public void show() {
        System.out.print("粉饰【");
        showable.show();
        System.out.print("】");
    }
}

说明:

  1. 化妆品装饰器类与女生类一样也实现了标准行为展示接口Showable,这说明它同样能够进行展示,只是方式可能比较独特。构造方法中,化妆品装饰器类在构造自己的时候可以把其他可展示者注入进来并赋给定义的引用。如此一来,化妆品装饰器类中包含的这个可展示者就成为一个“被装饰者”的角色了。注意第10行的展示方法show(),化妆品装饰器类不但调用了“被装饰者”的展示方法,而且在其前后加入了自己的“粉饰效果”,这就像加了一层“壳”一样,包裹了被装饰对象。

4.客户端类

客户端类代码干净、利落,我们将构造出来的女生类实例作为参数传给化妆品装饰器类的构造方法,这就好像为女生外表包裹了一层化妆品一样,对象结构非常生动、形象。接着,我们调用的是化妆品的展示方法show(),运行结果立竿见影,除女生自己的素颜展示结果之外还加上了额外的化妆效果。

public class Client {
    public static void main(String[] args) {
        new Decorator(new Girl()).show();
    }
}
//输出结果
粉饰【女生的素颜】

二、化妆品的多样性

至此,我们已经完成了基本的装饰工作,可是装饰器中只有一个简单的“粉饰”效果,这未免过于单调,我们是否忘记了“口红”的效果?除此之外,可能还会有“眼线”“睫毛膏”“腮红”等各种各样的化妆品。

如何让我们的装饰器具备以上所有装饰功效呢?有些读者可能会想到,把这些装饰操作统统加入化妆品装饰器类中,一次搞定所有化妆操作。这样的做法必然是错误的,试想,难道每位女生都习惯于如此浓妆艳抹吗?化妆品的多样性决定了装饰器应该是多态化的,单个装饰器应该只负责自己的化妆功效,例如口红只用于涂口红,眼线笔只用于画眼线,把化妆品按功能分类才能让用户更加灵活地自由搭配,用哪个或不用哪个由用户自己决定,而不是把所有功能都固化在同一个装饰器里。

可能又有读者提出了别的解决方案,化妆品装饰器类已经是展示接口Showable的实现了,这本身已经使多态化成为了可能,那么让所有化妆品类都实现Showable接口不就行了吗?没错,但还记得化妆品装饰器类中出现的被装饰者引用(代码清单9-3的第3行)吗?有没有想过,难道每个化妆品类里都要引用这个被装饰者吗?粉底类里需要加入,口红类里也需要加入……这显然会导致代码冗余。

诚然,Showable接口是能够满足多态化需求的,但它只是对行为接口的一种规范,极度的抽象并不具备对代码继承的功能,所以化妆品的多态化还需要接口与抽象类的搭配使用才能两全其美。装饰器类的抽象化势在必行,我们来看如何重构它。

1. 装饰器抽象类

public abstract class Decorator implements Showable{
    //被装修的展示者
    Showable showable;

    public Decorator(Showable showable) {
        this.showable = showable;
    }

    @Override
    public void show() {
        showable.show();
    }
}

说明:

  1. 我们将化妆品装饰器类修改为装饰器抽象类,这主要是为了不允许用户直接实例化此类。接着我们重构了展示方法show(),其中只是调用了被装饰者的show()方法,而不再做任何装饰操作,至于具体如何装饰则属于其子类的某个化妆品类的操作范畴了。

2.装饰器实现

//粉底类
public class FoundationMakeup extends Decorator{

    public FoundationMakeup(Showable showable) {
        super(showable);
    }

    @Override
    public void show() {
        System.out.print("打粉底【");
        showable.show();
        System.out.print("】");
    }
}
//口红类
public class Lipstick extends Decorator{
    public Lipstick(Showable showable) {
        super(showable);
    }

    @Override
    public void show() {
        System.out.print("涂口红【");
        showable.show();
        System.out.print("】");
    }
}

说明:

  1. 粉底类不用去实现Showable接口了,而是继承了装饰器抽象类,如此父类中对被装饰者的定义得以继承,可以看到我们在构造方法中调用了父类的构造方法并注入被装饰者,这便是继承的优势所在。当然,这个粉底类的show()方法一定要加上自己特有的操作,我们在调用被装饰者的show()方法前后都进行了打粉底操作。

3.客户端类

与粉底类同出一辙,口红类只是进行了自己特有的“涂口红”操作。最后,客户端可以依次把被装饰者“女生”、装饰器“粉底”、装饰器“口红”用构造方法层层包裹起来,再进行展示即可完成整体化妆工作,

public class Client {
    public static void main(String[] args) {
        // 口红包裹粉底,粉底再包裹女生
        Showable madeupGirl = new Lipstick(new FoundationMakeup(new Girl()));
        madeupGirl.show();
    }
}
//输出结果
涂口红【打粉底【女生的素颜】】

说明:

  1. 客户端类中出现了多层的构造方法操作,接着只调用装饰好的madeupGirl对象的展示方法show(),所有装饰效果一触即发,层层递归。需要注意的是一系列构造产生的顺序,我们最终得到的madeupGirl对象本质上引用的是口红,口红里包裹了粉底,粉底里又包裹了女生,
  2. 至此,装饰器模式重构完毕,化妆品多态化得以顺利实现。如果用户对这些淡妆效果不够满意,我们还可以接着添加其他化妆品类,以便用户自由搭配出自己的理想效果,使“清新淡妆”或“浓妆艳抹”均成为可能。

三、无处不在的装饰器

通过对装饰器模式的学习,读者是否觉得这种如同“俄罗斯套娃”一般层层嵌套的结构似曾相识?有些读者可能已经想到了,没错,其实装饰器模式在Java开发工具包(Java Development Kit, JDK)里就有大量应用,例如“java.io”包里一系列的流处理类InputStream、FileInputStream、BufferedInputStream、ZipInputStream等。举个例子,当对压缩文件进行解压操作时,我们就会用构造器嵌套结构进行文件流装饰,请参看代码

//I/O流处理类的应用
File file = new File("/压缩包.zip");
ZipInputStream zipInputStream = new ZipInputStream(
     new BufferedInputStream(
         new FileInputStream(file)
    )
);

说明:

  1. 我们首先以文件file初始化并构造文件输入流FileInputStream,然后外层用缓冲输入流BufferedInputStream进行装饰,使文件输入流具备内存缓冲的功能,最外层再用压缩包输入流ZipInputStream进行最终装饰,使文件输入流具备Zip格式文件的功能,之后我们就可以对压缩包进行解压操作了。当然,针对不同场景,Java I/O提供了多种流操作处理类,让各种装饰器能被混搭起来以完成不同的任务。

总结

提示:这里对文章进行总结:

  1. Java类库中对装饰器模式的应用当然要比我们的例程复杂得多,但基本思想其实是一致的。装饰器模式最终的目的就在于“装饰”对象,其中装饰器抽象类扮演着至关重要的角色,它实现了组件的通用接口,并且使自身抽象化以迫使子类继承,使装饰器固定特性的延续与多态化成为可能。
  2. 装饰器模式的各角色定义如下。
  • Component(组件接口):所有被装饰组件及装饰器对应的接口标准,指定进行装饰的行为方法。对应本章例程中的展示接口Showable。
  • Composite(复合组件):需要被装饰的组件,实现组件接口标准,只具备自身未被装饰的原始特性。对应本章例程中的女生类Girl。
  • Decorator(装饰器):装饰器的高层抽象类,同样实现组件接口标准,且包含一个被装饰的组件。
  • ConcreteDecorator(装饰器实现):继承自装饰器抽象类的具体子类装饰器,可以有多种实现,在被装饰组件对象的基础上为其添加新的特性。对应本章例程中的粉底类FoundationMakeup、口红类Lipstick。
  1. 装饰器模式可以将不同功能的单个模块规划至不同的装饰器类中,各装饰器类独立自主,各司其职。客户端可以根据自己的需求自由搭配各种装饰器,每加一层装饰就会有新的特性体现出来,巧妙的设计让功能模块层层叠加,装饰之上套装饰,最终使原始对象的特性动态地得到增强。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhixuChen200

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值