Java设计模式百例 - 装饰器模式

本文源码见:https://github.com/get-set/get-designpatterns/tree/master/decorator

装饰器模式(Decorator Pattern)以客户端透明的方式扩展对象的功能。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装,是继承关系的一个替代方案。

说到装饰者模式,估计大家都不陌生,Java I/O的设计就是采用了装饰者模式。想必初学Java I/O的时候大家都经历过一段“懵逼”期,各种InputStreamOutputStream层层嵌套,感觉就像洋葱,如果给装饰者一个形象化的吉祥物,想必非洋葱莫属。

例子

这里装饰者的例子就直接拿来主义了,因为《图解设计模式》这本书中的例子比较形象。

这个例子的功能是给文字增加装饰边框。通过不同的装饰类实现不同的边框效果。

比如,我有个字符串“Hello, world!”,我想在两侧加上边框,那么就显示为

|Hello, world!|

如果我想在上下左右均加上边框,那么就显示为

+-------------+
|Hello, world!|
+-------------+

并且可以一层层嵌套:

+---------------+
|+-------------+|
||Hello, world!||
|+-------------+|
+---------------+

怎么样,是不是很像洋葱啊?

总体来说,还是字符显示的问题,通过一个抽象类Display来定义。

Display.java

public abstract class Display {
public abstract int getColumn();
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));
}
}
}

其中show()方法将所有行的内容显示出来。那么对于没有任何边框的文字显示来说:

StringDisplay.java

public class StringDisplay extends Display {
    private String string;

    public StringDisplay(String string) {
        this.string = string;
    }

    public int getColumn() {
        return string.getBytes().length;
    }

    public int getRows() {
        return 1;
    }

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

因为只有一行,所以getRows()返回1。我们再来看一下边框装饰后的文本:

BoardStringDisplay.java

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

SideBoardStringDisplay.java

public class SideBoardStringDisplay extends BoardStringDisplay {

    protected SideBoardStringDisplay(Display display) {
        super(display);
    }

    public int getColumns() {
        return 1 + display.getColumns() + 1;    // 文字两侧各增加一个字符
    }

    public int getRows() {
        return display.getRows();               // 行数不变
    }

    public String getRowText(int row) {
        return "|" + display.getRowText(row) + "|";
    }
}

FullBoardStringDisplay.java

public class FullBoardStringDisplay extends BoardStringDisplay {

    protected FullBoardStringDisplay(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 || row == display.getRows() + 1) {
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else  {
            return "|" + display.getRowText(row - 1) + "|";
        }
    }

    private String makeLine(char ch, int count) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}

试一下洋葱效果:

Client.java

public class Client {
    public static void main(String[] args) {
        Display d1 = new StringDisplay("Hello, world!");
        Display d2 = new SideBoardStringDisplay(d1);
        Display d3 = new FullBoardStringDisplay(
                        new SideBoardStringDisplay(
                            new FullBoardStringDisplay(d2)));
        System.out.println("显示字符串>>>>>>");
        d1.show();
        System.out.println("\n增加两侧边框>>>>>>");
        d2.show();
        System.out.println("\n再增加全边框、两侧边框、全边框>>>>>>");
        d3.show();
    }
}

输入如下:

result.png

总结

这个例子的代码量比以前的多一些,但是思路并不复杂。

decorator.png

这里,StringDisplay是被装饰者,SideBoardStringDisplayFullBoardStringDisplay是装饰器,同时也能够被装饰,因为说到底,它们都是继承自Display,所以可以层层嵌套,不断增强。

我们再回头看装饰器模式的特点:

  1. 接口透明,在不改变被装饰者的前提下增加功能。无论是装饰器还是被装饰者,都有共同的抽象,也许是继承同一个抽象类,也许是实现同一个接口;如此一来,装饰前后的对象都是对外提供同样的服务。就像生日蛋糕,无论是装饰了草莓、还是慕斯、还是巧克力,都还是蛋糕,不会变成一块披萨。
  2. 使用了委托装饰器将被装饰的对象作为成员,使得类之间称为弱关联关系,这一点和桥接模式的出发点是一致的。草莓点缀在生日蛋糕上是草莓生日蛋糕,点缀在披萨上是水果披萨,客人不能抢了主人风头。这里和代理模式有些类似,主要区别在于应用场景和目的,装饰器模式应当为所装饰的对象提供增强功能,而代理模式对被代理的对象施加控制,并不提供对对象本身的增强。

各种装饰器并非一定要有一个抽象(本例中的BoardStringDisplay),直接装饰StringDisplay也是OK的,这个并不是装饰器模式的特点,只是具体使用时看是否有进一步抽象的需要。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351