装饰者模式

业务场景:

火锅店有3种火锅底锅:鸳鸯锅,香辣锅,菌菇锅;有4种可加的菜:生菜,豆腐,羊肉,牛肉。如下图所示。顾客可以店一个底锅,配任任意菜(数量和种类都任意),那么顾客可能点的火锅种类就有点多了:鸳鸯锅加生菜加豆腐加羊肉,香辣锅加生菜加羊肉加牛肉,菌菇锅加3份羊肉加5份牛肉(豪),鸳鸯锅加1份生菜(生菜我都不想点)......在我们的程序中不可能穷举出所有的组合而事先实现所有的种类,我们只有让顾客在选择菜品时不断地动态地生成新的种类来满足顾客的需求。

动态生成新的种类,可以在之前已有的菜品的基础上增加生成新的菜品。也就是往已有的菜品中加菜,进行装饰或者包裹。如下图所示,按照用户点菜的顺序依次包裹形成最终的菜品。 这种采用层层包裹层层点缀生成新对象的方式就叫做装饰者模式。


实现

使用装饰者模式的首要要求就是装饰者和被装饰者必须具有相同的超类型。如下图所示,生菜、豆腐等装饰者都继承自HotPot类,这种继承结构总是让人感觉不舒服,因为毕竟火锅中加的菜一般我们不称之为火锅。但是,这种模式状态下请忽略这种字面的纠结,我们使用继承只是为了有正确的类型而不是继承它的行为。因为有了正确的类型,我们可以在对被装饰者进行装饰时,可以调用相同的api并对装饰者的进行行为的扩展。

public abstract class HotSpot {

    String description = "不知道什么火锅";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

public class TwoFlavorHotPot extends HotSpot {

    public TwoFlavorHotPot() {
        description = "鸳鸯锅";
    }

    @Override
    public double cost() {
        return 48;
    }
}

public class SpicyHotPot extends HotSpot{

    public SpicyHotPot() {
        description = "香辣锅";
    }

    @Override
    public double cost() {
        return 58;
    }
}

public class MushroomHotSpot extends HotSpot{

    public MushroomHotSpot() {
        description = "菌菇锅";
    }

    @Override
    public double cost() {
        return 38;
    }
}

public abstract class CondimentDecorator extends HotSpot{
    public abstract String getDescription();
}

public class Lettuce extends CondimentDecorator {

    HotSpot hotSpot ;
    public Lettuce(HotSpot hotSpot) {
        this.hotSpot = hotSpot;
    }

    @Override
    public String getDescription() {
        return hotSpot.getDescription() + "加一份生菜";
    }

    @Override
    public double cost() {
        return hotSpot.cost() + 10;
    }
}

public class Tofu extends CondimentDecorator {

    HotSpot hotSpot;

    public Tofu(HotSpot hotSpot) {
        this.hotSpot = hotSpot;
    }

    @Override
    public String getDescription() {
        return hotSpot.getDescription() + "加一份豆腐";
    }

    @Override
    public double cost() {
        return hotSpot.cost() + 10;
    }
}

public class Mutton extends CondimentDecorator {

    HotSpot hotSpot ;
    public Mutton(HotSpot hotSpot) {
        this.hotSpot = hotSpot;
    }

    @Override
    public String getDescription() {
        return hotSpot.getDescription() + "加一份羊肉";
    }

    @Override
    public double cost() {
        return hotSpot.cost() + 58;
    }
}

public class Beaf extends CondimentDecorator {

    HotSpot hotSpot ;
    public Beaf(HotSpot hotSpot) {
        this.hotSpot = hotSpot;
    }

    @Override
    public String getDescription() {
        return hotSpot.getDescription() + "加一份牛肉";
    }

    @Override
    public double cost() {
        return hotSpot.cost() + 48;
    }
}

测试

public class Test {

    public static void main(String[]args){
        //单锅底
        HotSpot hotSpot = new MushroomHotSpot();
        System.out.println(hotSpot.getDescription() + "。价格:" + hotSpot.cost()+"。");
        //一份生菜的鸳鸯锅
        HotSpot hotSpot1 = new TwoFlavorHotPot();
        hotSpot1 = new Lettuce(hotSpot1);
        System.out.println(hotSpot1.getDescription() + "。价格:" + hotSpot1.cost()+"。");
        //样样都有的香辣锅
        HotSpot hotSpot2 = new SpicyHotPot();
        hotSpot2 = new Lettuce(hotSpot2);
        hotSpot2 = new Tofu(hotSpot2);
        hotSpot2 = new Mutton(hotSpot2);
        hotSpot2 = new Beaf(hotSpot2);
        System.out.println(hotSpot2.getDescription() + "。价格:" + hotSpot2.cost()+"。");
        
    }
}

菌菇锅。价格:38.0。
鸳鸯锅加一份生菜。价格:58.0。
香辣锅加一份生菜加一份豆腐加一份羊肉加一份牛肉。价格:184.0。

从上述的测试代码中我们可以看出,我们可以对既有的底锅进行任意菜品的添加,可以在不用修改原有代码的基础上动态生成顾客所需的菜,满足顾客的需求。

总结

优点

  • 装饰者和被装饰者对象具有相同的超类型。继承是为了有正确的类型,而不是继承它的行为。利用继承达到“类型匹配”,而不是利用继承获得“行为”。
  • 可以用一个或者多个装饰者包裹一个对象。
  • 既然装饰者和被装饰者具有相同的超类型,所以在任何需要原始对象(包裹对象)的场合,可以使用装饰过的对象代替它。
  • 装饰者可以在被装饰者的行为之前或者之后加上自己的行为,已达到特定的目标,例如此例中的生成描述信息和最终价格。
  • 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

缺点

  • 生成很多很多的小类,代码层面比较啰嗦。如例所示,我们要创建各种装饰对象并且不停地进行new操作来完成包裹。
  • 在装饰的过程中,由于装饰者没有窥视被装饰的对象,所以在此过程中,无法针对特定的类型进行特定操作,例如此例中对鸳鸯锅进行8折的优惠操作,那么在不断地装饰过程中,后面的装饰者是不好进行被装饰者类型的判读的。
  • 进行行为的扩展只能一次装饰一次扩展,装饰者没有判断其他装饰者的存在的能力,例如此例中加2份羊肉的话,最终生成的描述信息是“...加一份羊肉加一份羊肉...”而不是“...加2份羊肉...”,要达到这种效果就需要额外增加代码了。

参考

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

推荐阅读更多精彩内容