装饰器模式

525 阅读7分钟

装饰器模式

Reference

[1] bugstack.cn/md/develop/…

[2] c.biancheng.net/view/1397.h…

[3] refactoringguru.cn/design-patt…

[4] cmsblogs.com/article/140…

[5] blog.csdn.net/lovelion

什么是装饰器模式

装饰器(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。

场景

假设你正在开发一个提供通知功能的库, 其他程序可使用它向用户发送关于重要事件的通知。

最初的版本基于 Notifier 类,用来接受客户端消息参数并发送给一系列邮箱,邮箱列表通过构造函数传递。

image-20211216101709401

后续有新的需求增加, 用户要求能够在手机短信、微信、QQ上接收消息,那么可以这样做,扩展通知类,在子类中加入额外的通知方法,然后组合多种通知方法即可解决问题

弊端

  • 系统扩展麻烦,在某些编程语言中无法实现,Java 中是不支持多继承的,所有每次要扩展一项功能,就必须新增一个类
  • 代码重复,可以发现发送消息的代码在多个类中重复
  • 系统庞大,类的数目非常多
image-20211216102016632

解决方案

根本原因在于,这个设计的复用机制不合理,上图采用的是继承复用的机制,而我们知道,根据设计原则,应该多用组合,少用继承。

  • 继承是静态的。 你无法在运行时更改已有对象的行为, 只能使用由不同子类创建的对象来替代当前的整个对象。
  • 子类只能有一个父类。 大部分编程语言不允许一个类同时继承多个类的行为。

因此我们可以换个角度,将 send 方法这个通知行为放在基类通知器中,将所有其他方法放入装饰中。

image-20211216102754495

这样,客户端代码就可以以装饰器的模式封装自己想要的功能

image-20211216102929189

装饰模式结构

结构1

  • 单个具体构件
  • 没有抽象构件

如果只有一个具体构件而没有抽象构件时,可以让抽象装饰继承具体构件,也就是上面的 Notifier 的例子

image-20211216105043872

结构2

  • 只有一个具体装饰

如果只有一个具体装饰时,可以将抽象装饰和具体装饰合并

image-20211216105311048

结构3

这个是经典的装饰器结构

  • 有多个具体构件类
  • 又有抽象构件类
image-20211216103239894
  1. 部件,也叫抽象构件 (Component) 声明封装器和被封装对象的公用接口。
  2. 具体部件,也叫具体构件 (Concrete Component) 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为。
  3. 基础装饰 (Base Decorator) 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。
  4. 具体装饰类 (Concrete Decorators) 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为。
  5. 客户端 (Client) 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。

核心设计

装饰模式的核心在于抽象装饰类的设计,其典型代码如下所示:

class Decorator implements Component
{
    //维持一个对抽象构件对象的引用
    private Component component;  

    //注入一个抽象构件类型的对象
    public Decorator(Component component)  {
        this.component=component;
    }
    
    public void operation( ) {
        component.operation( );  //调用原有业务方法
    }
}

在抽象装饰类Decorator中定义了一个Component类型的对象component,维持一个对抽象构件对象的引用,并可以通过构造方法或Setter方法将一个Component类型的对象注入进来,同时由于Decorator类实现了抽象构件Component接口,因此需要实现在其中声明的业务方法operation(),需要注意的是在Decorator中并未真正实现operation()方法,而只是调用原有component对象的operation()方法,它没有真正实施装饰,而是提供一个统一的接口,将具体装饰过程交给子类完成。

在Decorator的子类即具体装饰类中将继承operation()方法并根据需要进行扩展,典型的具体装饰类代码如下:

class ConcreteDecorator extends Decorator {

    public ConcreteDecorator(Component component) {
        super(component);

    }

    public void operation() {
        super.operation();  //调用原有业务方法
        addedBehavior();  //调用新增业务方法

    }

    //新增业务方法
    public void addedBehavior() {
        ……
    }

}

在具体装饰类中可以调用到抽象装饰类的operation()方法,同时可以定义新的业务方法,如 addedBehavior()

由于在抽象装饰类Decorator中注入的是Component类型的对象,因此我们可以将一个具体构件对象注入其中,再通过具体装饰类来进行装饰;此外,我们还可以将一个已经装饰过的Decorator子类的对象再注入其中进行多次装饰,从而对原有功能的多次扩展。

案例设计

Sunny软件公司基于面向对象技术开发了一套图形界面构件库VisualComponent,该构件库提供了大量基本构件,如窗体、文本框、列表框等,由于在使用该构件库时,用户经常要求定制一些特效显示效果,如带滚动条的窗体、带黑色边框的文本框、既带滚动条又带黑色边框的列表框等等,因此经常需要对该构件库进行扩展以增强其功能

直接利用装饰器模式,进行系统设计,使系统具有更好的灵活性和扩展性

image-20211216103812787

具体代码

//抽象界面构件类:抽象构件类,为了突出与模式相关的核心代码,对原有控件代码进行了大量的简化
abstract class Component {
    public abstract void display();
}

//窗体类:具体构件类
class Window extends Component {
    public void display() {
        System.out.println("显示窗体!");
    }
}

//文本框类:具体构件类
class TextBox extends Component {
    public void display() {
        System.out.println("显示文本框!");
    }
}

//列表框类:具体构件类
class ListBox extends Component {
    public void display() {
        System.out.println("显示列表框!");
    }
}


//构件装饰类:抽象装饰类
class ComponentDecorator extends Component {

    private Component component;  //维持对抽象构件类型对象的引用

    //注入抽象构件类型的对象
    public ComponentDecorator(Component component)  {
        this.component = component;
    }

    public void display() {
        component.display();
    }

}

//滚动条装饰类:具体装饰类
class ScrollBarDecorator extends ComponentDecorator {

    public ScrollBarDecorator(Component component) {
        super(component);
    }

    public void display() {
        this.setScrollBar();
        super.display();
    }

    public void setScrollBar() {
        System.out.println("为构件增加滚动条!");
    }

}


//黑色边框装饰类:具体装饰类
class BlackBorderDecorator extends ComponentDecorator {

    public BlackBorderDecorator(Component component) {
        super(component);
    }

    public void display() {
        this.setBlackBorder();
        super.display();
    }

    public void setBlackBorder() {
        System.out.println("为构件增加黑色边框!");
    }

}

客户端代码

class Client{
    public  static void main(String args[])  {
        Component  component,componentSB,componentBB; //全部使用抽象构件定义
        component = new Window();
        componentSB = new  ScrollBarDecorator(component);
        //将装饰了一次之后的对象继续注入到另一个装饰类中,进行第二次装饰
        componentBB = new  BlackBorderDecorator(componentSB); 
        componentBB.display();
    }
    
    // 也可以这样写
    Component  component;
    component =  new  BlackBorderDecorator(  new  ScrollBarDecorator( new Window( )));
}

装饰器模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。

BufferedReader in = new BufferedReader(new FileReader("filename.txt"));
String s = in.readLine();

小结

装饰器模式的主要优点有:

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

进阶阅读

如果您想深入了解装饰器模式,可猛击阅读以下文章。