设计模式(八)—— 装饰器

52 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情

概念

正如其名字一样,装饰器模式(Decorator)能够在运行时动态地为原始对象增加一些额外的功能,使其变得更加强大。

从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能,区别在于方式的不同,后者是在编译时(compile-time)静态地通过对原始类的继承完成,而前者则是在程序运行时(run-time)通过对原始对象动态地“包装”完成,是对类实例(对象)“装饰”的结果。

实例演示

注:本文内容参考 《秒懂设计模式》一书,本文对其做了概括凝练,主要是为了自身学习使用,如果无法理解,建议查看原书。

既然是装饰器模式,那么我们以一个常见的例子——化妆来讲解一下什么是装饰器模式。

1. 定义展示行为(原始对象)

首先,对于任何妆容,我们都会有一个展示的行为 show(),我们将其抽象为接口 Showable,代码如下:

public interface Showable {

    public void show();//标准展示行为

}

接下来,我们来实现这个行为,因为目前我们还没有使用任何的化妆效果,所以展示的只是素颜。代码如下:

public class Girl implements Showable{

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

}

2. 化妆品修饰(装饰器)

接下来我们就要使用化妆品来实现化妆功能了,那么我们怎么实现呢?Girl 类里面 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("】");//粉饰结束
    }

}

化妆品装饰器类与女生类一样也实现了标准行为展示接口 Showable,即这它同样能够进行展示,只是方式可能比较独特。化妆品装饰器类不但调用了“被装饰者”的展示方法,而且在其前后加入了自己的“粉饰效果”,这就像加了一层“壳”一样,包裹了被装饰对象。

接下来我们来看看客户端的运行结果:

public class Client {

    public static void main(String[] args) {
        //用装饰器包裹女孩后再展示
        new Decorator(new Girl()).show();

        //运行结果:粉饰【女生的素颜】
    }

}

我们调用装饰器的 show() 方法,并将 Girl 实例作为参数传递进去,效果就像为女生外表加上化妆品一样。

3. 化妆品多样化(装饰器类抽象)

上面的代码中我们已经实现了基本的装饰工作,但是装饰器中只有一个简单的“粉饰”效果,这未免过于单调,“口红”、“眼线”、“睫毛膏”等等的化妆品,那么我们该如何实现呢?如何让我们的装饰器具备以上所有装饰功效呢?

也许我们想到可以一次搞定所有化妆操作。这样的做法必然是错误的,难道每位女生都习惯于如此浓妆艳抹吗?

当然,也有同学想到了,既然化妆品装饰器类已经实现了 Showable 接口,那么让所有化妆品类都实现Showable接口不就行了吗?但是,如果我们这样实现的话,那么每个化妆品装饰类都会出现被装饰者的引用,这显然会导致代码冗余。

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

下面我们来看一下装饰器抽象类的实现方法:

public abstract class Decorator implements Showable{

    protected Showable showable;

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

    @Override
    public void show() {
        showable.show();//直接调用不加任何装饰
    }

}

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

例如口红类实现如下:

public class Lipstick extends Decorator{

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

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

相应的,客户端代码如下:

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

总结

装饰器模式的各角色定义如下:

  • Component(组件接口):所有被装饰组件及装饰器对应的接口标准,指定进行装饰的行为方法。对应本章例程中的展示接口 Showable。
  • ConcreteComponent(组件实现):需要被装饰的组件,实现组件接口标准,只具备自身未被装饰的原始特性。对应本章例程中的女生类 Girl。
  • Decorator(装饰器):装饰器的高层抽象类,同样实现组件接口标准,且包含一个被装饰的组件。
  • ConcreteDecorator(装饰器实现):继承自装饰器抽象类的具体子类装饰器,可以有多种实现,在被装饰组件对象的基础上为其添加新的特性。对应本章例程中的粉底类 FoundationMakeup、口红类 Lipstick。

参考文档

  • 《秒懂设计模式》—— 刘韬