设计模式之装饰者模式
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的方案。
来解析这个定义:
- “动态”指的在运行时进行的,而不是在编译时,也即客户端调用时可以临时决定增加责任到对象上。
- “责任附加”意味着可以在不修改原有对象的情况下,增加新的行为或功能。
- “比继承更有弹性”,说明使用的是组合而不是继承,通过组合可以在运行时动态地添加或移除功能。弹性意味可以组合多个能力,而不需要创建大量子类。
装饰者最重要的核心是装饰者和被装饰者来自于同一个超类。相比较于策略模式、观察者模式,难以理解一点,并不是一个自然就能想到的结构。我们从源头来看看它的设计脉络。
先来一个自然能想到的简单实现:
// Text 类
public class PlainText {
private String content;
public PlainText(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
// Format 工具类
public class Format {
public static void bold(PlainText text) {
text.setContent("<b>" + text.getContent() + "</b>");
}
public static void italic(PlainText text) {
text.setContent("<i>" + text.getContent() + "</i>");
}
}
// Client
public class Client {
public static void main(String[] args) {
PlainText text = new PlainText("Hello, World!");
// 动态添加格式
Format.bold(text); // 加粗
Format.italic(text); // 斜体
System.out.println(text.getContent());
// 输出:<i><b>Hello, World!</b></i>
}
}
在这个实现中,优点是代码逻辑清晰,易于理解,要改动渲染顺序,需要改动Client中的代码实现。
但利用设计模式原则来审视,会有以下问题:
- 违法了开闭原则:如果要新增如删除线格式,需要改动Format类,添加新的方法。
- 强耦合:这点十分重要,需要真正做到理解,Format依赖的是具体的PlainText类,而不是依赖抽象,这与依赖倒置原则不相符,并且setContent方法表明了Format操作了PlainText的具体属性,这是一种深度耦合,当content从String变成List时,Format就需要进行改动。(注意:getContent于setContent不同,是一种松耦合)
进行优化,先解决开闭原则的问题,将bold、italic方法抽离成BoldDecorate、ItalicDecorate类,当然来自于同一个超类(因为需求是要随时替换顺序),这个思路在策略模式中也用到。再解决强耦合问题,不应该耦合具体的PlainText类,而应该依赖接口Text。

public interface Text {
String getContent();
}
public interface Decorate {
String getContent();
}
public class PlainText implements Text {
private String content;
PlainText(String content) {
this.content = content;
}
public String getContent() {
retrun content;
}
}
public class BoldDecorate implements Decorate {
private Text text;
BoldText(Text text) {
this.text = text;
}
public String getContent() {
return "<b>" + text.getContent() + "</b>";
}
}
public class ItalicDecorate implements Decorate {
private Text text;
ItalicText(Text text) {
this.text = text;
}
public String getContent() {
return "<i>" + text.getContent() + "</i>";
}
}
public class client {
public static void main(String[] args) {
Text text = new PlainText("hello world")
Decorate decorateItalic = new ItalicDecorate(text);
// 无法增加Bold能力,decorateItalic类型不匹配
// Decorate decorateBold = new BoldText(?)
}
}
看到了吗,再完成一次功能增加后,无法继续了,因为Text与Decorate是两种不同类型的接口。因此,引出了装饰模式中最重要的一个点,即装饰类与被装饰类需来自于同一个超类。
我们让ItalicDecorate、BoldDecorate也实现Text接口。

public interface Text {
String getContent();
}
public class PlainText implements Text {
private String content;
PlainText(String content) {
this.content = content;
}
public String getContent() {
retrun content;
}
}
public class BoldDecorate implements Text {
private Text text;
BoldText(Text text) {
this.text = text;
}
public String getContent() {
return "<b>" + text.getContent() + "</b>";
}
}
public class ItalicDecorate implements Text {
private Text text;
ItalicText(Text text) {
this.text = text;
}
public String getContent() {
return "<i>" + text.getContent() + "</i>";
}
}
public class client {
public static void main(String[] args) {
Text text = new BoldDecorate(new ItalicDecorate(new PlainText("hello world")))
String content = text.getContent();
System.out.println(content);
}
}
看到Text text = new BoldDecorate(new ItalicDecorate(new PlainText("hello world")))
这个嵌套效果了吗,是不是似曾相识 。
DataInputStream dataIn = new DataInputStream(new BufferedInputStream(new FileInputStream("example.txt")));
java io典型调用
发现没有,不学习设计模式,都看不懂源码,不清楚为什么要如此设计,如此编码。
目前的实现已经完美了吗,其实还有优化空间,BoldDecorate、ItalicDecorate都维护了text属性和构造函数代码,这些可以利用一个抽象装饰基类解决,将 text
属性和构造函数提取到基类中,避免在每个具体装饰者中重复代码。

public interface Text {
String getContent();
}
public abstract class DecorateText implements Text {
Text text;
DecorateText(Text text) {
this.text = text;
}
public abstract String getContent();
}
public class PlainText implements Text {
private String content;
PlainText(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
public class BoldDecorate extends DecorateText {
BoldText(Text text) {
super(text);
}
public String getContent() {
return "<b>" + text.getContent() + "</b>";
}
}
public class ItalicDecorate extends DecorateText {
ItalicText(Text text) {
super(text);
}
public String getContent() {
return "<i>" + text.getContent() + "</i>";
}
}
public class client {
public static void main(String[] args) {
Text text = new BoldDecorate(new ItalicDecorate(new PlainText("hello world")));
String content = text.getContent();
System.out.println(content);
}
}