设计模式:Decorator模式

装饰器应该是用得比较多的,比如有一个蛋糕,加上奶油,就变成了奶油蛋糕;加上草莓,就变成草莓奶油蛋糕;加上蜡烛,就变成了生日蛋糕;虽然加了各种装饰品,但都依旧还是蛋糕,只不过加上了一些装饰后,目的也更加明确了

Decorator模式就是给对象添加装饰,和蛋糕类似,首先有一个相当于蛋糕的对象,然后不断地装饰蛋糕一样地不断给对象增加功能,就变成了使用目的更明确的对象

下面这个例子,给一个字符串添加边框装饰物

相关的类如下

Display:显示字符串的抽象类

StringDisplay:显示单行字符串的类

Border:显示装饰边框的抽象类

SideBorder:显示左右边框的类

FullBorder:显示上下左右边框的类

Main:main,测试类

首先是Display抽象类,显示多行字符串的抽象类

getColumns和getRows分别获取横向字符数和纵向行数;getRowText获取指定某行的字符串

show显示所有行的字符串;方法内部基本和Template Method设计模式一致,调用本抽象类里的抽象方法按行打印出每行的字符串内容

package decorator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Display
* Author: lihui
* Date: 2018/5/9 20:06
*/

public abstract class Display {
public abstract int getColumns();
public abstract int getRows();
public abstract String getRowText(int row);
public final void show() {
for (int i = 0; i < getRows(); i++) {
System.out.println(getRowText(i));
}
}
}

StringDisplay类,相当于生日蛋糕中的核心蛋糕,显示单行字符串,继承Display,基本就是实现抽象方法

string字段保存要显示的字符串,但由于StringDisplay类只显示一行字符串,因此getColumns方法返回长度,getRows方法返回常数1;当要获取第0行的内容时getRowText方法才会返回string字段

package decorator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: StringDisplay
* Author: lihui
* Date: 2018/5/9 20:06
*/

public class StringDisplay extends Display {
private String string;
public StringDisplay(String string) {
this.string = string;
}

@Override
public int getColumns() {
return string.getBytes().length;
}

@Override
public int getRows() {
return 1;
}

@Override
public String getRowText(int row) {
if (row == 0) {
return string;
} else {
return null;
}
}
}

Border类,装饰边框的抽象类,继承了Display类,表示的是装饰边框;这样通过继承,装饰边框和被装饰物有了相同的方法,也就是具有一致性

Display类型的display字段,表示被装饰物,但是display字段表示的被装饰物不限于StringDisplay类型实例,因为Border也是Display类的子类,display字段锁表示的也可能是其他的装饰边框,如Broder类的子类的实例,而且那个边框中也有一个display字段

package decorator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Border
* Author: lihui
* Date: 2018/5/9 20:09
*/

public abstract class Border extends Display {
protected Display display;
protected Border(Display display) {
this.display = display;
}
}

SideBorder类,继承Border类,是一种具体的装饰边框,通过指定borderchar字符装饰字符串的左右两侧;实现了超类中所有抽象方法,因此不是抽象类

package decorator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: SideBorder
* Author: lihui
* Date: 2018/5/9 20:10
*/

public class SideBorder extends Border {
private char borderChar;
public SideBorder(Display display, char ch) {
super(display);
this.borderChar = ch;
}

@Override
public int getColumns() {
return 1 + display.getColumns() + 1;
}

@Override
public int getRows() {
return display.getRows();
}

@Override
public String getRowText(int row) {
return borderChar + display.getRowText(row) + borderChar;
}
}

FullBorder类,继承Border类,也是一种具体的装饰;会在字符串的四周都加上装饰边框,SideBorder类可以指定边框的字符,FullBorder类边框字符固定

makeLine方法可以连续地显示某个指定的字符,作为一个工具方法

package decorator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: FullBorder
* Author: lihui
* Date: 2018/5/9 20:10
*/

public class FullBorder extends Border {
public FullBorder(Display display) {
super(display);
}

@Override
public int getColumns() {
return 1 + display.getColumns() + 1;
}

@Override
public int getRows() {
return 1 + display.getRows() + 1;
}

@Override
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) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < count; i++) {
sb.append(ch);
}
return sb.toString();
}
}

最后是Main,生成了4个实例

b1:将Hello, world直接显示

b2:在b1的两侧加上装饰边框#

b3:在b2的上下左右加上装饰边框

b4:为Hello, world加上多重边框

package decorator;

/**
* Copyright (C), 2014-2018, maoxiaomeng.com
* FileName: Main
* Author: lihui
* Date: 2018/5/9 20:10
*/

public class Main {
public static void main(String[] args) {
Display b1 = new StringDisplay("Hello, world");
Display b2 = new SideBorder(b1, '#');
Display b3 = new FullBorder(b2);
b1.show();
b2.show();
b3.show();
Display b4 =
new SideBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new FullBorder(
new StringDisplay("Hello, world")
),
'*'
)
)
),
'/'
);
b4.show();
}
}

最后运行的结果

Hello, world
#Hello, world#
+--------------+
|#Hello, world#|
+--------------+
/+------------------+/
/|+----------------+|/
/||*+------------+*||/
/||*|Hello, world|*||/
/||*+------------+*||/
/|+----------------+|/
/+------------------+/

Process finished with exit code 0

 

Decorator模式中的角色

Component:例子中的Display类;增加功能时的核心蛋糕

ConcreteComponent:例子中的StringDisplay类;实现了Component角色定义的接口的具体蛋糕

Decorator:装饰物,例子中的Border类;与Component角色有相同的接口,内部保存了被修饰对象Component角色,Decorator角色知道自己要装饰的对象

ConcreteDecorator:具体的装饰物,例子中的SideBorder类和FullBorder类;具体的Decorator角色

 

思考:

Decorator模式中,装饰边框与被装饰物具有一致性;例子中装饰边框类Border是被装饰物类Display的子类,并且具有相同的接口,体现了它们之间的一致性

这样一来,即使被装饰物被边框装饰起来了,接口API也不会被隐藏起来,其它类依然可以调用getColumns,getRows,getRowText以及show方法,这就是接口的透明性;b4实例被装饰了多次,但是接口却没有发生任何变化

由于接口的透明性,Decorator模式中也形成了类似Composite模式中的递归结构,也就是说,装饰边框里面的被装饰物实际上又是别的物体的装饰边框,但是与Composite模式使用目的不同,主要是通过添加装饰物来增加对象的功能

在Decorator模式中,装饰边框与被装饰物具有相同的接口,虽然接口是相同的,但是越装饰,功能越多;例子中用SideBorder装饰Display后,就可以在字符串的左右两侧加上装饰字符;如果用FullBorder装饰,就可以在字符串的四周加上边框;此时完全不需要对被装饰的类做任何修改,这样就实现了不修改被装饰的类即可增加功能

Decorator模式使用了委托,对装饰边框提出的要求(调用装饰边框的方法)会被转交(委托)给被装饰物去处理,例子中SideBorder类的getColumns方法调用了display.getColumns(),除此之外,getRows方法也调用了display.getRows()

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

发表回复