装饰器模式 -- 装饰与被装饰物的一致性

1. 概述

假设我们有一个蛋糕,如果我们给它加上了奶油,就变成了奶油蛋糕,如果给它加上了草莓,就成了草莓蛋糕,如果加上巧克力,就是巧克力蛋糕了。这就是装饰蛋糕的场景。
我们的程序也是一样,可以给如同给蛋糕装饰一样,不断为其增加功能。这时候就需要装饰器模式出场了。也许你会觉得,我们用一般的继承不就能满足设计了吗?
那么请考虑这样的情况,我们有一块蛋糕,我们想加上奶油做出奶油蛋糕,然后又想要一块奶油草莓蛋糕,还想要一块奶油巧克力蛋糕,那么如果我们使用简单的继承设计,是否就会需要实现两个新的类型,却同时拥有奶油的属性?
在一个继承体系中,一般来说各子类应该是相斥的,比如汽车的子类可以是奥迪品牌,或者宝马品牌,这必然是两个互斥的子类,但是对汽车的装潢却是可以兼容的,无论是奥迪还是宝马汽车,都可以选择贴膜,或者换轮毂。
所以对这些可以兼容的功能的添加,就非常适合装饰器模式。其价值就在于装饰,而不影响被装饰物类本身的核心功能。

2. 模式类图

decorator-1.png

3. 模式角色

Component
增加功能时的核心角色。上文书中的蛋糕就是 Component 角色。

ConcreteComponent
实现 Component 角色定义接口的角色,也就是具体的被装饰物实例。

Decorator(装饰物)
该角色具有 Component 角色相同的接口。并且在其内部保存了被装饰对象 -- Component 角色。Decorator 角色知道自己要装饰的对象。

ConcreteDecorator(具体的装饰物)
具体实现装饰物的角色。

4. 代码示例

这里我们用装饰 String 输出的例子来说明装饰模式。我们会为字符串添加装饰的边框,代码非常直观的体现了装饰器模式的意义。

首先定义一个 Component 角色,这里创建一个抽象类。

Display.java

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));
        }
    }
}

接着定义一个 ConcreteComponent 角色,这就是那块蛋糕。

StringDisplay.java

public class StringDisplay extends Display {
    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;
        }
        return null;
    }
}

然后是 Decorator 角色,这里定义一个抽象类。

Border.java

public abstract class Border extends Display {
    protected Display display;

    protected Border(Display display) {
        this.display = display;
    }
}

最后定义两个 ConcreteDecorator 角色。分别实现不同的装饰边框。

SideBorder.java

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.java

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 || 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 方法调用

Main.java

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

推荐阅读更多精彩内容

  • 定义 装饰器模式又名包装(Wrapper)模式。装饰器模式以对客户端透明的方式拓展对象的功能,是继承关系的一种替代...
    步积阅读 35,317评论 0 38
  • (转载)原文地址 在阎宏博士的《JAVA与模式》一书中开头是这样描述装饰(Decorator)模式的: 装饰模式又...
    zjk_00阅读 633评论 0 2
  • 本篇文章介绍一种设计模式——装饰者模式。装饰者模式在Java中的典型应用就是IO流,在本篇文章中将有详细介绍。本篇...
    Ruheng阅读 22,203评论 13 56
  • 在阎宏博士的《JAVA与模式》一书中开头是这样描述装饰(Decorator)模式的: 装饰模式又名包装(Wrapp...
    聂叼叼阅读 347评论 1 2
  • 1 概述 在一个项目中,你会有非常多的因素考虑不到,特别是业务的变更,不时的冒出一个需求是很正常的情况。有三个继承...
    今晚打肉山阅读 300评论 0 0