三、装饰者模式(Decorator)

本章可以称为“给爱用继承的人一个全新的设计眼界” ,我们即将再度探讨典型的继承滥用问题。你将在本章学到如何使用对象组合的方式,做到在运行时装饰类。一旦你熟悉了装饰的技巧,你将能够在不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责。

本章目录如下:

        一、阶段一

        二、阶段二

        三、阶段三

        四、java中的装饰者

        五、模式问答

        六、设计原则总结

本章需求是设计一个星巴克点咖啡应用,并通过代码迭代、优化过程得出前人的设计经验之一装饰者模式。首先我们先分析需求的变化之处:创造新调制方式的咖啡和原料价格变化。我们所设计的系统必须松耦合,且能够很好的适应变化才行。下面以代码的迭代演化过程为线索介绍。

一、阶段一

    需求分析(最差设计):首先,我们要明确咖啡是一个对象,是一个由各种原料调配出来的对象,所以不能像超市购物一样罗列所有原料来计算价钱,所以每一种咖啡都对应一个类。阶段一不把公共操作抽象出来继承,每种咖啡各自实现自己的功能。类图如下:

    缺点:

            1、代码复用度很低,几乎没有。

            2、弹性非常低,一点也不能应对变化。比如某一调料价格变化后,更改每个咖啡价格的工作是灾难性的。

二、阶段二

    需求分析:把公共操作抽象出来,通过继承实现每种咖啡。类图如下:

    缺点:只能适应部分变化,如调料价格变化;但是有些变化不适应,如创造新调制方式的咖啡时不能复用代码。最终要的是违反了“开放—关闭”原则。且子类数量还是爆炸式增长。

三、阶段三

    继承的缺点+组合的优点:利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。即通过动态地组合对象,可以通过写新代码来添加新功能,而无须修改现有代码。这样就没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。

    我的理解:组合并不是抛弃继承,它是多个继承的组合,等于说以前那个庞大的继承被拆分了。

    如何评作设中的好坏?对于变化是否能做到使原有代码免于修改!

    何为变化?从不同纬度看增、删、改、查!

    设计原则4(开放—关闭原则):类应该对扩展开放,对修改关闭。该原则目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。这样的设计具有弹性,可以接受新的功能来应对需求的改变。

    阶段三需求分析:以饮料为主体,然后在运行时以调料来“装饰”饮料。可以把装饰对象理解为一个空心管,只要符合指定接口的对象都可以插入空心管进行包装,包装后的对象符合指定接口就可以被再次包装。比如要制作一个加奶泡(Whip)加摩卡(mocha)的深焙咖啡(DarkRoast),那么包装图如下:

阶段三的类图如下:

    读者可能不太理解上图,下面我们说明一下装饰者模式是什么,然后读者就明白了。

    装饰者模式定义:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。装饰者模式的类图如下:

    下面说明阶段三的代码:

======================类图中的Beverage基类=================

public abstract class Beverage {

    String description = "Unknown Beverage";  

    public String getDescription() {

        return description;

    } 

    public abstract double cost();

}

======================类图中的调料抽象类CondimentDecorator=================

public abstract class CondimentDecorator extends Beverage {

    public abstract String getDescription();//使所有的调料装饰者都必须重新实现该方法,因为在调料具体实现类中我们要返回调料名+被装饰者的getDescription()对象,所以此处必须抽象化,以保证子类必须实现该方法

}

======================类图中的饮料实现类=================

public class Espresso extends Beverage {   //浓缩咖啡

    public Espresso() {

        description = "Espresso";

    }  

    public double cost() {

        return 1.99;

    }

}

public class HouseBlend extends Beverage {//综合咖啡

    public HouseBlend() {

        description = "House Blend Coffee";

    } 

    public double cost() {    

        return .89;

    }

}

public class DarkRoast extends Beverage {//深焙咖啡

    public DarkRoast() {

        description = "Dark Roast Coffee";

    } 

    public double cost() {

        return .99;

    }

}

public class Decaf extends Beverage {//低咖啡因

    public Decaf() {

        description = "Decaf Coffee";

    } 

    public double cost() {

        return 1.05;

    }

}

======================类图中的装饰者实现类,即调料实现类=================

public class Mocha extends CondimentDecorator {//摩卡

    Beverage beverage; 

    public Mocha(Beverage beverage) {

        this.beverage = beverage;

    } 

    public String getDescription() {

        return beverage.getDescription() + ", Mocha";

    } 

    public double cost() {

        return .20 + beverage.cost();

    }

}

public class Milk extends CondimentDecorator {//奶

    Beverage beverage;

    public Milk(Beverage beverage) {

        this.beverage = beverage;

    }

    public String getDescription() {

        return beverage.getDescription() + ", Milk";

    }

    public double cost() {

        return .10 + beverage.cost();

    }

}

public class Soy extends CondimentDecorator {//豆浆

    Beverage beverage;

    public Soy(Beverage beverage) {    

        this.beverage = beverage;

    }

    public String getDescription() {

        return beverage.getDescription() + ", Soy";

    }

    public double cost() {

        return .15 + beverage.cost();

    }

}

public class Whip extends CondimentDecorator {//奶泡

    Beverage beverage; 

    public Whip(Beverage beverage) {

        this.beverage = beverage;

    } 

    public String getDescription() {

        return beverage.getDescription() + ", Whip";

    } 

    public double cost() {

        return .10 + beverage.cost();

    }

}

======================供应咖啡,即点餐=================

public class StarbuzzCoffee {  public static void main(String args[]) {

     //浓缩咖啡,不需要调料

    Beverage beverage = new Espresso();

    System.out.println(beverage.getDescription()  + " $" + beverage.cost()); 

    //深焙咖啡,加双份摩卡,加一份奶泡

    Beverage beverage2 = new DarkRoast();

    beverage2 = new Mocha(beverage2);

    beverage2 = new Mocha(beverage2);

    beverage2 = new Whip(beverage2);

    System.out.println(beverage2.getDescription()  + " $" + beverage2.cost()); 

    //综合咖啡,加豆浆、摩卡、奶泡

    Beverage beverage3 = new HouseBlend();

    beverage3 = new Soy(beverage3);

    beverage3 = new Mocha(beverage3);

    beverage3 = new Whip(beverage3);

    System.out.println(beverage3.getDescription()  + " $" + beverage3.cost());

}

}

四、java中的装饰者

java中最常见的装饰者io类图如下:

Java IO引出装饰者模式的“缺点”:

    (1)、装饰者模式常常造成设计中有大量的小类,可能会造成使用此API程序员的困扰。但是,现在你已经了解了装饰者的工作原理,以后当使用别人的大量装饰的API时,就可以很容易地辨别出他们的装饰者类是如何组织的,以方便用包装方式取得想要的行为。

    (2)、采用装饰者在实例化组件时将增加代码的复杂度。因为一旦使用装饰者模式,不只需要实例化组件,还要把此组件包装进装饰者中,可能会有很多个。但是,可以通过工厂模式(Factory)/生成器模式(Builder)封装装饰者模式的创建过程来解决该问题。

下面我们编写一个自己的java/io装饰者,代码如下

public class LowerCaseInputStream extends FilterInputStream {

    public LowerCaseInputStream(InputStream in) {

        super(in);

    } 

    public int read() throws IOException {

        int c = in.read();

        return (c == -1 ? c : Character.toLowerCase((char)c));

    }

    public int read(byte[] b, int offset, int len) throws IOException {

        int result = in.read(b, offset, len);

        for (int i = offset; i < offset+result; i++) {

            b[i] = (byte)Character.toLowerCase((char)b[i]);

        }

        return result;

    }

}

public class InputTest {

    public static void main(String[] args) throws IOException {

        int c;

        try {

            InputStream in =  new LowerCaseInputStream( new BufferedInputStream( new FileInputStream("test.txt")));

            while((c = in.read()) >= 0) {

                System.out.print((char)c);

            }

            in.close();

        } catch (IOException e) {

        e.printStackTrace();

        }

    }

}


五、模式问答

1、如何让设计的每个部分都遵循开放-关闭原则?

    答: 通常,你办不到。要让00设计同时具备开放性和关闭性,又不修改现有的代码,需要花费许多时间和努力。一般来说,我们实在没有闲工夫把设计的每个部分都这么设计(而且,就算做得到,也可能只是一种浪费)。遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。你需要把注意力集中在设计中最有可能改变的地方,然后应用开放-关闭原则。

2、使用装饰者模式,你必须管理更多的对象,所以犯错的机会会增加。那么如何避免呢?

    装饰者通常是用其他类似于工厂或生成器这样的模式来包装的。一旦我们讲到这两个模式,你就会明白具体的组件及其装饰者的创建过程,它们会“封装得很好” ,所以不会有这种问题。

3、装饰者知道这一连串装饰链条中其他装饰者的存在吗?

    不能。装饰者该做的事就是增加行为到被包装对象上。当需要窥视装饰者链中的每一个装饰者时,这就超出了他们的天赋了。

六、设计原则总结

设计原则1:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化 T个T的代码混在一起。该设计原则作用: “把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分”。

设计原则2:针对接口编程,而不是针对实现编程。===我的理解是这个原则的使用优先级排在“设计原则1:变化原则”之后,即该原则是一个在大模式已确定需要实现具体类时针对实现类采用的原则,针对范围比较小。

设计原则3:为了交互对象之间的松耦合设计而努力。总结为“多用组合少用继承”目前我见过两种组合方式:实例作为另外实例的属性,如策略模式、装饰者模式;实例作为另外实例的集合属性的成员,如观察者模式

设计原则4(开放—关闭原则):类应该对扩展开放,对修改关闭。该原则目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。这样的设计具有弹性,可以接受新的功能来应对需求的改变。

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

推荐阅读更多精彩内容

  • 引言 在介绍装饰者模式之前,我们先了解一个设计原则: 多用组合,少用继承。 在平时写代码时,我们应该减少类继承的使...
    Zentopia阅读 4,081评论 4 11
  • 代理模式 为其他类提供一个对象以控制这个对象的访问。 这就有意思了,你可以如果你是代理,那么你可以增强你代理对象的...
    Myth52125阅读 180评论 0 0
  • 一、定义 装饰模式:动态地给一个被装饰者对象添加其他兄弟类一些额外的职责,但是不改变被装饰者类的功能。就增加功能来...
    innovatorCL阅读 327评论 0 0
  • 上篇文章提到了Context及其子类源码分析(一),这篇文章我们来讲讲Context及其子类用到的设计思想——装饰...
    小阿拉阅读 941评论 0 1
  • 我,不是大款,也不是大官, 我,没有权势,也没有金钱, 我只是一个平平凡凡的人, 如草一样普通,如水一样清澈。 走...
    乮嵽阅读 404评论 2 5