设计模式之装饰模式

概述

装饰模式(Decorator)也叫包装器模式(Wrapper),是指动态地给一个对象添加一些额外的职责,就增加功能来说装饰模式比生成子类更为灵活。它通过创建一个包装对象,也就是装饰来包裹真实的对象

情景举例

我们先来分析这样一个画图形的需求:

  1. 它能绘制各种背景,如红色、蓝色、绿色
  2. 它能绘制形状,如三角形,正方形,圆形
  3. 它能给形状加上阴影

就先列这三个简单的需求吧,下面让我们比较下各种实现的优缺点

丑陋的实现

来看看我们用继承是如何实现的,首先,抽象出一个Shape接口我想大家都不会有意见的是不是?

/**
 * @author HansChen
 */
public interface Shape {

    /**
     * 绘制图形
     */
    void draw();
}

然后我们定义各种情况下的子类,结构如下,看到这么多的子类,是不是有点要爆炸的感觉?真是想想都可怕


2019-9-2-12-35-9.png

而且如果再新增一种需求,比如现在要画椭圆,那么维护的人员估计就要爆粗了吧?

为了避免写出上面的代码,聪明的童鞋们可能会提出第二种方案:

/**
 * @author HansChen
 */
public class ShapeImpl implements Shape {

    enum Type {
        Circle,
        Square,
        Trilatera
    }

    enum Color {
        Red,
        Green,
        Blue
    }

    private Type    type;
    private Color   color;
    private boolean shadow;

    public ShapeImpl() {
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public Color getColor() {
        return color;
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public boolean isShadow() {
        return shadow;
    }

    public void setShadow(boolean shadow) {
        this.shadow = shadow;
    }

    @Override
    public void draw() {
        // TODO: 2017/3/9 根据属性情况画出不同的图
    }
}

这样,根据不同的画图需求,只需要设置不同的属性就可以了,这样确实避免了类爆炸增长的问题,但这种方式违反了开放封闭原则,比如画正方形的方式变了,需要对ShapeImpl进行修改,或者如果新增需求,如画椭圆,也需要对ShapeImpl进行修改。而且这个类不方便扩展,子类将继承一些对自身并不合适的方法。

装饰模式

概念介绍

装饰模式(Decorator)也叫包装器模式(Wrapper),是指动态地给一个对象添加一些额外的职责

以下情况使用Decorator模式:

  • 需要扩展一个类的功能,或给一个类添加附加职责。
  • 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
  • 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类

但这种灵活也会带来一些缺点,这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂

下面来看看装饰模式的结构:


2019-9-2-12-35-32.png
  1. Component抽象组件,是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象。(注:在装饰模式中,必然有一个最基本、最核心、最原始的接口或者抽象类充当Component抽象组件)
  2. ConcreteComponent具体组件,是最核心、最原始、最基本的接口或抽象类的实现,我们需要装饰的就是它
  3. Decorator装饰角色, 一般是一个抽象类,实现接口或者抽象方法,它的属性里必然有一个private变量指向Component抽象组件。
  4. 具体装饰角色,如上图中的ConcreteDecoratorA和ConcreteDecoratorB,我们要把我们最核心的、最原始的、最基本的东西装饰成其它东西。

代码示例如下:

 /**
 * @author HansChen
 */
public interface Component {

    void operation();
}
public class ConcreteComponent implements Component {

    @Override
    public void operation() {
        System.out.print("do something");
    }
}
public class Decorator implements Component {

    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
    }
}
public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        System.out.println("do something");
    }
}
public class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        System.out.println("do something");
    }
}

上面说了一堆结构和示例代码,但大家可能还是不太好理解,下面用装饰模式来重新实现画图的功能

用装饰模式实现需求

先上结构图


2019-9-2-12-35-51.png

首先定义可动态扩展对象的抽象

public interface Shape {

    /**
     * 绘制图形
     */
    void draw();
}

定义具体的组件,每一个组件代表一个形状

public class Square implements Shape {

    @Override
    public void draw() {
        System.out.print("正方形");
    }
}
public class Trilateral implements Shape {

    @Override
    public void draw() {
        System.out.print("三角形");
    }
}
public class Circle implements Shape {

    @Override
    public void draw() {
        System.out.print("圆形");
    }
}

定义可装饰者的抽象类

public class ShapeDecorator implements Shape {

    private Shape shape;

    public ShapeDecorator(Shape shape) {
        this.shape = shape;
    }

    @Override
    public void draw() {
        shape.draw();
    }
}

定义具体的装饰者

public class Blue extends ShapeDecorator {

    public Blue(Shape shape) {
        super(shape);
    }

    @Override
    public void draw() {
        super.draw();
        System.out.print(" 蓝色");
    }
}
public class Green extends ShapeDecorator {

    public Green(Shape shape) {
        super(shape);
    }

    @Override
    public void draw() {
        super.draw();
        System.out.print(" 绿色");
    }
}
public class Red extends ShapeDecorator {

    public Red(Shape shape) {
        super(shape);
    }

    @Override
    public void draw() {
        super.draw();
        System.out.print(" 红色");
    }
}
public class Shadow extends ShapeDecorator {

    public Shadow(Shape shape) {
        super(shape);
    }

    @Override
    public void draw() {
        super.draw();
        System.out.print(" 有阴影");
    }
}

好了,现在让我们看看具体怎么使用:

public class Test {

    public static void main(String[] args) {
        //正方形 红色 有阴影
        Shape shape = new Square();
        shape = new Red(shape);
        shape = new Shadow(shape);
        shape.draw();

        //圆形 绿色
        shape = new Circle();
        shape = new Green(shape);
        shape.draw();

        //三角形 蓝色 有阴影
        shape = new Trilateral();
        shape = new Blue(shape);
        shape = new Shadow(shape);
        shape.draw();
    }
}

可以看到,装饰模式是非常灵活的,通过不同的装饰,实现不同的效果

装饰模式的应用举例

这里再列举一些用到了装饰模式的情景,童鞋们可以根据这些场景加深对装饰模式的理解

  • Java中IO设计
  • Android中ContextContextWrapper的设计

总结

装饰模式是为已有功能动态地添加功能的一种方式,它把每个要装饰的功能放在单独的类中,并让这个类包括要装饰的对象,有效地把核心职能和装饰功能区分开了。但它带来灵活的同时,也容易导致别人不了解自己的设计方式,不知如何使用。就像Java中I/O库,人们第一次接触的时候,往往无法轻易理解它。这其中的平衡取舍,就看自己咯

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

推荐阅读更多精彩内容