• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

图解设计模式(12):Decorator模式(装饰边框与被装饰物的一致性)

设计模式 winrains 1年前 (2019-09-20) 52次浏览

不断地为对象添加装饰的设计模式被称为Decorator模式。Decorator指的是“装饰物”。

1 Decorator模式中的角色

  • Component

增加功能时的核心角色。Component角色只定义接口。在示例中,对应Display类。

  • ConcreteComponent

实现了Component角色所定义的接口。在示例中,对应StringDisplay类。

  • Decorator(装饰物)

该角色具有与Component角色相同的接口。在它内部保存了被装饰对象Component角色。Decorator角色知道自己要装饰的对象。在示例中,对应Border类。

  • ConcreteDecorator(具体的装饰物)

该角色是具体的Decorator角色。在示例中,对应SideBorder类和FullBorder类。

2 Decorator模式的类图

3 示例程序

示例程序的功能是给文字添加装饰边框。这里所谓的装饰边框是指用“-”、“+”、“|”等字符组成的边框。

3.1 类一览表

名字 说明
Display 用于显示字符串的抽象类
StringDisplay 用于显示单行字符串的类
Border 用于显示装饰边框的抽象类
SiderBorder 用于只显示左右边框的类
FullBorder 用于显示上下左右边框的类
Main 测试程序行为的类

3.2 类图

3.3 示例代码

Display类

可以显示多行字符串的抽象类,在show方法里使用了getRowsgetRowText等抽象方法,属于Template Method模式。

public abstract class Display {
    public abstract int getColumns();               // 获取横向字符数
    public abstract int getRows();                  // 获取纵向行数
    public abstract String getRowText(int row);     // 获取第row行的字符串
    public void show() {                            // 全部显示
        for (int i = 0; i < getRows(); i++) {
            System.out.println(getRowText(i));
        }
    }
}

StringDisplay类

用于显示单选字符串的类。String字段中保存的是要显示的字符串。

public class StringDisplay extends Display {
    private String string;                          // 要显示的字符串
    public StringDisplay(String string) {           // 通过参数传入要显示的字符串
        this.string = string;
    }
    public int getColumns() {                       // 字符数
        return string.getBytes().length;
    }
    public int getRows() {                          // 行数是1
        return 1;
    }
    public String getRowText(int row) {             // 仅当row为0时返回值
        if (row == 0) {
            return string;
        } else {
            return null;
        }
    }
}

Border类

是装饰边框的抽象类,也是Display类的子类。
通过继承,装饰边框与被装饰物具有了相同的方法。从接口角度而言,装饰边框(Border)与被装饰物(Display)具有相同的方法也就着它们具有一致性。
在装饰边框Border类中有一个Display类型的display字体,它表示被装饰物。不过,display字段所表示的装饰物并不仅限于StringDisplay的实例。因为Border也是Display类的子类,display字段所表示的也可能是其它装饰边框(Border类的子类的实例),而且那个边框中也有一个display字段。

public abstract class Border extends Display {
    protected Display display;          // 表示被装饰物
    protected Border(Display display) { // 在生成实例时通过参数指定被装饰物
        this.display = display;
    }
}

SideBorder类

是一种具体的装饰边框,是Border类的子类。SideBorder类采用指定的字符(borderchar)装饰字符串的左右两侧。

public class SideBorder extends Border {
    private char borderChar;                        // 表示装饰边框的字符
    public SideBorder(Display display, char ch) {   // 通过构造函数指定Display和装饰边框字符
        super(display);
        this.borderChar = ch;
    }
    public int getColumns() {                       // 字符数为字符串字符数加上两侧边框字符数
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {                          // 行数即被装饰物的行数
        return display.getRows();
    }
    public String getRowText(int row) {             // 指定的那一行的字符串为被装饰物的字符串加上两侧的边框的字符
        return borderChar + display.getRowText(row) + borderChar;
    }
}

FullBorder类

是一种具体的装饰边框,也是Border类的子类。FullBorder类会在字符串的上下左右都加上装饰边框。

public class FullBorder extends Border {
    public FullBorder(Display display) {
        super(display);
    }
    public int getColumns() {                   // 字符数为被装饰物的字符数加上两侧边框字符数
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {                      // 行数为被装饰物的行数加上上下边框的行数
        return 1 + display.getRows() + 1;
    }
    public String getRowText(int row) {         // 指定的那一行的字符串
        if (row == 0) {                                                 // 上边框
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else if (row == display.getRows() + 1) {                      // 下边框
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else {                                                        // 其他边框
            return "|" + display.getRowText(row - 1) + "|";
        }
    }
    private String makeLine(char ch, int count) {         // 生成一个重复count次字符ch的字符串
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}

Main类

测试程序。在Main类中一共生成了4个实例,即b1~b4,作用分别如下:

  • b1:将“Hello, world.”不加装饰地直接显示出来
  • b2:在b1的两侧加上装饰边框’#’
  • b3:在b2的上下左右加上装饰边框
  • b4:为“你好,世界。”加上多重边框

运行结果

Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+------------------------+/
/|+----------------------+|/
/||*+------------------+*||/
/||*|你好,世界。|*||/
/||*+------------------+*||/
/|+----------------------+|/
/+------------------------+/

4 总结

4.1 接口的透明性

在Decorator模式中,装饰边框与被装饰物具有一致性。在示例程序中,表示装饰边框的Border类是表示被装饰物的Display类的子类,这就体现了它们之间的一致性。也就是说,Border类(以及它的子类)与表示被装饰物的Display类具有相同的接口。
这样,即使被装饰物被边框装饰起来了,接口了不会被隐藏起来。在示例中,实例b4被装饰了多次,但是接口却没有发生任何变化。
得益于接口的透明性,Decorator模式也可形成了类似于Composite模式中的递归结构。也就是说,装饰边框里面的“被装饰物”实际上又是别的物体的“装饰边框”。不过,Decorator模式虽然与Composite模式一样,都具有递归结构,但是它们的使用目的不同。Decorator模式的主要目的是通过添加装饰物来增加对象的功能。

4.2 在不改变装饰物的前提下增加功能

在Decorator模式中,装饰边框与被装饰物具有相同的接口。虽然接口相同,但是越装饰,功能则越多。完全不需要对被装饰的类作任何修改,就实现了不修改装饰的类即可增加功能。
Decorator模式使用了委托。对“装饰边框”提出的要求(调用装饰边框的方法)会被转交(委托)给“被装饰物”去处理。

4.3 可以动态地增加功能

Decorator模式中用到了委托,它使类之间形成了弱关联关系。因此,不用改变框架代码,就可以生成一个与其它对象具有不同关系的新对象。

4.4 只需要一些装饰物即可添加许多功能

使用Decorator模式可以为程序添加许多功能。只要准备一些装饰边框(ConcreteDecorator角色),即使这些装饰边框都只具有非常简单的功能,也可以将它们自由组合成为新的对象。

4.5 导致增加许多很小的类

Decorator模式的一个缺点是会导致程序中增加许多功能类似的很小的类。

摘自《图解设计模式》


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (1)