设计模式学习(三)——装饰者模式

一.需求

小王的便利店里卖柴鸡蛋,而且很受欢迎,一下子让小王夫妇打开了思路:原来只要细心观察小区居民的需求,可以卖的东西还有很多。小王的媳妇儿小芳琢磨着在便利店门口卖煎饼果子!小区里住着不少上班族,他们每天早上急匆匆去上班,一个热乎的煎饼果子又营养,又美味,一定会受到大家喜欢的!

说干就干,一周后,小芳的煎饼摊就正式开始营业了。果然,不一会就排起来长队……一份标准的煎饼果子是5元,放一个鸡蛋,一块薄脆,再撒上葱花,香菜,小芳又别处心裁,加了少许榨菜,咬一口,薄脆的酥脆,煎饼的松软,再加上榨菜的清爽……绝了!

民以食为天,小小的煎饼果子也能吃出不少名堂。有的想多加一个鸡蛋,有的想要两块薄脆,有的想多加一根火腿……不一而足,加一个鸡蛋要多加5角,加一块薄脆5角,加一个火腿1元,大家口味各异,把小芳忙的不可开交。算账容易出错不说,还大大影响了效率。

小王不愧是程序员出身,看到老婆的难处,当即决定给老婆写一个软件用来给煎饼果子收款。

二.初步尝试

尝试一

在仔细分析了面临的问题之后,小王觉得:问题主要在于很多顾客想要一些“豪华版”的煎饼果子,例如加一个鸡蛋,加一个火腿,加一块薄脆,在标准的煎饼果子之外,只要把常见的搭配设计好就可以了。小王的设计如下:

// 标准煎饼果子
class ChineseHamburger {
    private static final float price = 5.0f;

    public float cost() {
        return price;
    }
}

// 标准煎饼果子加一份薄脆
class CHAddCrisp extends  ChineseHamburger {
    private static final float price = 0.5f;

    public float cost() {
        return super.cost() + price;
    }
}

// 标准煎饼果子加一个鸡蛋
class CHAddEgg extends  ChineseHamburger {
    private static final float price = 1.0f;

    public float cost() {
        return super.cost() + price;
    }
}

// 标准煎饼果子加一根火腿
class CHAddHam extends  ChineseHamburger {
    private static final float price = 1.0f;

    public float cost() {
        return super.cost() + price;
    }
}
public class ChineseHamburgerAdmin {
    public static void main(String[] args) {
        ChineseHamburger ch = new ChineseHamburger();
        System.out.println("标准煎饼果子售价" + ch.cost());

        ChineseHamburger chAddEgg = new CHAddEgg();
        System.out.println("标准煎饼果子加鸡蛋售价" + chAddEgg.cost());

        ChineseHamburger chAddCrisp = new CHAddCrisp();
        System.out.println("标准煎饼果子加薄脆售价" + chAddCrisp.cost());
    }
}

类图如下:

运行程序:

标准煎饼果子售价5.0
标准煎饼果子加鸡蛋售价6.0
标准煎饼果子加薄脆售价5.5

这样就大大方便卖夹饼果子了,只要根据客户的需要选择相应的煎饼果子,就可以直接显示价格,不用自己算了,媳妇用着很顺手,小王脸上乐开了花……

没过多久,新的问题又出现了:有的客户想加两根火腿,有的客户想加一个鸡蛋再加一个火腿……客户的需求总是多种多样,又让小王的媳妇应接不暇。

看来,之前设计的那些组合根本不够用,难道还要再加CHAddHamAddEgg、CHAddTwoHam……?显示这不是一个理想的方案,天知道顾客们还会有什么特殊的口味呢?

尝试二
// 标准煎饼果子
class ChineseHamburger {
   private static final float PRICE = 5.0f; // 煎饼果子价格

   private static final float CRISP_PRICE = 0.5f; // 薄脆价格

   private static final float EGG_PRICE = 1.0f; // 鸡蛋价格

   private static final float HAM_PRICE = 1.0f; // 火腿价格

   private int addCrispNum; // 需要加多少薄脆

   private int addEggNum; // 需要加多少鸡蛋

   private int addHamNum; // 需要加多少火腿

   public ChineseHamburger(int addCrispNum, int addEggNum, int addHamNum) {
       this.addCrispNum = addCrispNum;
       this.addEggNum = addEggNum;
       this.addHamNum = addHamNum;
   }

   // 计算煎饼以及附加的原料的价格
   public float cost() {
       return PRICE
               + addCrispNum * CRISP_PRICE
               + addEggNum * EGG_PRICE
               + addHamNum * HAM_PRICE;
   }
}

public class ChineseHamburgerAdmin {
   public static void main(String[] args) {
       ChineseHamburger ch = new ChineseHamburger(0, 0, 0);
       System.out.println("标准煎饼果子售价" + ch.cost());

       ChineseHamburger chAddEgg = new ChineseHamburger(0, 1, 0);
       System.out.println("标准煎饼果子加鸡蛋售价" + chAddEgg.cost());

       ChineseHamburger chAddCrisp = new ChineseHamburger(1, 0, 0);
       System.out.println("标准煎饼果子加薄脆售价" + chAddCrisp.cost());

       ChineseHamburger chAddCrispAddEgg = new ChineseHamburger(1, 1, 0);
       System.out.println("标准煎饼果子加薄脆加鸡蛋售价" + chAddCrispAddEgg.cost());
   }
}

这个方案比之前的方案代码少了很多,既避免了类爆炸,又使得逻辑更灵活,现在随便加多少鸡蛋,加多少火腿都可以从容应对了。

谁知,又过了几周,大家对加火腿,加鸡蛋渐渐的也吃腻了,顾客就是上帝,这帮上帝可是真难伺候啊!小王和媳妇绞尽脑汁想丰富口味,他们又尝试了在煎饼里加肉松,加培根,加土豆丝……

但是这样一来,之前的程序就又需要修改了:需要在ChineseHamburger类中再添加AddMeatFlossNum(加肉松数量)、AddBaconNum(加培根数量)、AddPotatoesNum(加土豆丝数量),并且需要修改cost方法。

这无疑违反了编程的一个基本原则:开闭原则。即代码应该对扩展开放,对修改关闭。换句话说,每当逻辑升级,最好通过新增代码实现,而不修改现有代码。

三.更好的方案

下面我们来看另一种实现。

// 抽象类:面饼,可以是煎饼果子,也可以是卷饼,手抓饼等等
abstract class Biscuit {

    abstract float cost();
}


// 煎饼果子继承面饼抽象类
class ChineseHamburger extends Biscuit{
    private static final float PRICE = 5.0f; // 煎饼果子价格

    @Override
    public float cost() {
        return PRICE;
    }
}

// 面饼装饰者,针对当前场景,没有声明其他方法,只继承Biscuit中的方法
abstract class  BiscuitDecorator extends Biscuit{

}

// 面饼装饰者之一:薄脆
class Crisp extends BiscuitDecorator {

    private static final float PRICE = 0.5f;

    // 需要装饰的面饼
    private Biscuit biscuit;

    public Crisp(Biscuit biscuit) {
        this.biscuit = biscuit;
    }

    @Override
    float cost() {
        return biscuit.cost() + PRICE;
    }
}

// 面饼装饰者之一:鸡蛋
class Egg extends BiscuitDecorator {

    private static final float PRICE = 1.0f;

    // 需要装饰的面饼
    private Biscuit biscuit;

    public Egg(Biscuit biscuit) {
        this.biscuit = biscuit;
    }

    @Override
    float cost() {
        return biscuit.cost() + PRICE;
    }
}

// 面饼装饰者之一:火腿
class Ham extends BiscuitDecorator {

    private static final float PRICE = 0.5f;

    // 需要装饰的面饼
    private Biscuit biscuit;

    public Ham(Biscuit biscuit) {
        this.biscuit = biscuit;
    }

    @Override
    float cost() {
        return biscuit.cost() + PRICE;
    }
}
public class ChineseHamburgerAdmin {
    public static void main(String[] args) {
        Biscuit ch = new ChineseHamburger();
        System.out.println("标准煎饼果子售价" + ch.cost());

        // 用鸡蛋装饰煎饼果子
        Biscuit chAddEgg = new Egg(ch);
        System.out.println("标准煎饼果子加鸡蛋售价" + chAddEgg.cost());

        // 用薄脆装饰煎饼果子
        Biscuit chAddCrisp = new Crisp(ch);
        System.out.println("标准煎饼果子加薄脆售价" + chAddCrisp.cost());

        // 用薄脆装饰已经加了鸡蛋的煎饼果子
        Biscuit chAddCrispAddEgg = new Crisp(chAddEgg);
        System.out.println("标准煎饼果子加薄脆加鸡蛋售价" + chAddCrispAddEgg.cost());
    }
}

当前这种实现的类图如下:

这种实现的好处在于:灵活、扩展性强。煎饼果子和煎饼果子装饰者都继承自面饼类,装饰者可以随意对煎饼果子进行装饰,如果需要加肉松,之前的代码都无需修改,只要再实现一个肉松类继承BiscuitDecorator即可。同样,如果以后想卖卷饼、手抓饼也没问题,只要实现卷饼类实现Biscuit ,然后用装饰者装饰就可以了。

四.模式总结

想必大家已经看出来了,我们最后使用的方式就是装饰者模式了。

使用场景

当需要动态地增加责任和行为到对象上时。

例如我们都很熟悉了java IO类就大量的使用了装饰者模式,我们一般这样来声明一个输入流:

BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")));

使用InputStreamReader类装饰FileInputStream类,从而完成字节流到字符流的转换,再用BufferedReader类装饰InputStreamReader,实现有缓冲的读,优化性能。

类图

在装饰者模式中,主要有两种角色:组件和装饰者,装设者和组件继承自同一个父类,因此装饰者可以任意装饰其他组件,充分的利用多态的特性来进行行为扩展。

优点

1.可扩展性,添加新的行为无需修改已有代码
2.灵活性,可以根据自己的需求随意对组件进行装饰,动态的改变行为

缺点

1.会增加很多小类,每一种装饰行为需要实现一个特定的装饰类,增加维护成本
2.如果装饰者链很长,则增加了程序的复杂性,出现问题时排查成本高

参考资料:

1.《Head First设计模式》
2.设计模式读书笔记-----装饰者模式

本文已迁移至我的博客:http://ipenge.com/28811.html

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

推荐阅读更多精彩内容

  • 特有的品位、情趣、格调,被称之为小资情调。 我所理解的小资生活,是很舒服,很休闲的一种生活方式,唯一物一桌一椅而已...
    映月月影阅读 2,399评论 26 43
  • 外地人提起天津小吃,往往会想到盛名在外的“狗不理包子”。可是对于天津人而言,最日常接地气的小吃,同时也是最神圣不可...
    企鹅吃喝指南阅读 1,674评论 4 32
  • 现在都是这样她见我就跑,今天大富说她没啥事儿干,但放下好吃的就跑了。 如此可有两点思考 1大富是她好朋友,在点我?...
    阳丽茗阅读 243评论 0 0
  • 自动化好像是测试行业永恒不变的热点话题,同时也是测试行业争议最大的话题。不知道现在还有多少言论说自动化没有用的,也...
    吴小白吃阅读 741评论 0 2