装饰模式
装饰模式是不断地为对象添加装饰的设计模式。比如有一个蛋糕,添加了奶油后就是奶油蛋糕。再添加一些草莓后,就是草莓奶油蛋糕。
程序中的对象与蛋糕十分相似。首先有一个相当于蛋糕的对象,然后像不断的装饰蛋糕一样地不断对其增加功能,它就编程了使用目的更加明确的对象。
实例程序
先来看一个例子吧,给一行字符串添加左右边框、上线边框。
类图
代码
Display(显示字符串的抽象类,即被装饰的对象抽象类):
public abstract class Display {
abstract int getColumns();//获取横向字符数
abstract int getRows();//获取纵向字符数
abstract String getRowText(int i);//获取第row行的字符串
/**
* 显示所有字符
*/
public final void show() {
for (int i=0; i<getRows(); i++) {
System.out.println(getRowText(i));
}
}
}
StringDisplay(显示字符串的实现类,即被装饰的对象):
public class StringDisplay extends Display {
//要显示的字符串
private String string;
public StringDisplay(String string) {
this.string = string;
}
@Override
int getColumns() {
return string.getBytes().length; //字符数
}
@Override
int getRows() {
return 1; //行数为1
}
@Override
String getRowText(int row) {
if (row == 0) {
return string;//仅当row为0时返回值
}
return null;
}
}
Border(显示装饰边框的抽象类,即装饰对象的抽象类):
public abstract class Border extends Display {
protected Display display;//被装饰对象,装饰类必须持有该对象
protected Border(Display display) {
this.display = display;
}
}
SideBorder(装饰左右边框,即装饰对象):
public class SiderBorder extends Border {
//装饰边框的字符
private char borderChar;
public SiderBorder(Display display, char ch) {
super(display);
borderChar = ch;
}
/**
* 字符数为字符串字符数加上两侧边框字符数
* @return
*/
@Override
int getColumns() {
return 1 + display.getColumns() + 1;
}
/**
* 行数即被装饰物的行数
* @return
*/
@Override
int getRows() {
return display.getRows();
}
/**
* 指定的那一行的字符串为被装饰物的字符串加上两侧边框的字符
* @param i
* @return
*/
@Override
String getRowText(int i) {
return borderChar + display.getRowText(i) + borderChar;
}
}
FullBorder(装饰上下边框,即装饰对象):
public class FullBorder extends Border {
public FullBorder(Display display) {
super(display);
}
/**
* 字符数为字符串字符数加上两侧边框字符数
* @return
*/
@Override
int getColumns() {
return 1 + display.getColumns() + 1;
}
/**
* 行数即被装饰物的行数
* @return
*/
@Override
int getRows() {
return 1 + display.getRows() + 1;
}
/**
* 指定的那一行的字符串
* @param i
* @return
*/
@Override
String getRowText(int i) {
if (i == 0) {
return "+" + makeLine('-', display.getColumns()) + "+";//下边框
} else if (i == display.getRows()+1) {
return "+" + makeLine('-', display.getColumns()) + "+";//上边框
} else {
return "|" + display.getRowText(i - 1) + "|";//其他边框
}
}
/**
* 生成一个重复count次的字符ch的字符串
* @param ch
* @param count
* @return
*/
private String makeLine(char ch, int count) {
StringBuffer buf = new StringBuffer();
for (int i=0; i<count; i++) {
buf.append(ch);
}
return buf.toString();
}
}
DecoratorMain(测试类):
public class DecoratorMain {
public static void main(String[] args) {
//依次不断地装饰
Display b1 = new StringDisplay("Hello,world.");
Display b2 = new SiderBorder(b1, '#');
Display b3 = new FullBorder(b2);
b1.show();
b2.show();
b3.show();
//循环装饰
Display b4 = new SiderBorder(
new FullBorder(
new FullBorder(
new SiderBorder(
new FullBorder(
new StringDisplay("你好,世界。")
),
'*'
)
)
),
'/'
);
b4.show();
}
}
测试结果:
Hello,world.
#Hello,world.#
+--------------+
|#Hello,world.#|
+--------------+
/+------------------------+/
/|+----------------------+|/
/||*+------------------+*||/
/||*|你好,世界。|*||/
/||*+------------------+*||/
/|+----------------------+|/
/+------------------------+/
四个角色
- Component:被装饰的对象,是接口类或抽象类,如上述的Display
- ConcreteComponent:被装饰的具体对象,实现或继承Component,如上述的StringDisplay
- Decorator:装饰物,继承被装饰对象的接口类或抽象类Component,且内部持有Component对象,因为它知道要装饰的对象是谁,如上述中的Border
- ConcreteDecorator:具体装饰物,实现Decorator,如上述中的SideBorder、FullBorder
模式类图
java.io包
java.io包中的很多类都用到了装饰模式。比如:
Reader reader = new FileReader("test.txt");
Reader reader = new BufferedReader(new FileReader("test.txt"));
Reader reader = new LineNumberReader(
new BufferedReader(
new FileReader("test.txt")));
总结
- 装饰模式可以在不改变装饰物的前提下增加功能
- 可以自由组合,添加许多功能
- 若要扩展功能,装饰者提供了比继承更有弹性的替代方案
- 责任链和装饰者模式完成的是相同的事情
- 和代理模式区别:代理模式意在在代理中控制使用者对目标对象的访问以及进行功能增强,而装饰模式意在增加新功能
- 一个缺点是会导致程序中增加许多功能类似的很小的类