引言

在山西面馆吃鸡蛋面的时候突然想起装饰者这个模式,觉得面馆这个场景跟书中的星巴兹咖啡的场景很像,边吃边思考装饰者模式。这里也就依葫芦画瓢,换汤不换药的用装饰者模式来模拟一碗鸡蛋面是怎么出来的吧。吃货有吃货的方式来理解......这里先将书中讲到的例子放在前面,理论的东西,讲的还是比较具体的,只是觉得咖啡的例子不是太好理解,lz很土,几乎没喝过咖啡,不知道什么摩卡啊......,还是中国特色的例子更好理解。

为什么学设计模式?

答:觉得会设计模式的人,不仅仅是码农,更像艺术家!

为什么现在学设计模式?

答:不求精通,但求认识,接触过不少项目,有设计模式,而不认识,是我的损失,体会不到他的妙处,但不求它认识我。

装饰者到底装饰谁呢?

类,还是对象?

装饰者模式定义

动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。(由定义可知,装饰对象的)

实例分析

星巴兹咖啡订单管理系统 管理、计算各种饮料的售价。

若顾客根据个人喜好,添加不同的调料,那么系统就要有根据调料的不同来计算价格,按照原来的设计,必定会出现下面的情况。

第一种方案设计:继承

有多少种口味的咖啡,你就得建多少种对应的类。烦不?

第二种方案设计:

思考

当哪些因素改变时会影响这个设计?

调料价钱的改变会使我们更改现有代码。

一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。

以后可能会开发出新饮料,对于这些饮料而言(冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea子类仍将继承那些不合适的方法,比如:hasWhip()。
如何顾客想要双倍摩卡,怎么办?

......

设计原则(Open-Closed Principle)

类应该对扩展开放,对修改关闭。

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,其好处在于:这样的设计具有弹性,可以应对改变,可以接受新的功能来应对改变的需求。

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

这通常是无法做到的。要让OO设计同时具有开放性和关闭性,又不修改现有的代码,需要花费许多时间和努力。一般来说,我们没有足够的精力把设计的每个部分都这么设计,这可能只是一种浪费。
遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。你需要把注意力集中在设计中最有可能改变的地方,然后应用开放-关闭原则。

装饰者模式

星巴兹咖啡订单管理系统——使用装饰者模式

以饮料(Beverage)为主体,然后在运行时以调料(Condiment)来装饰(decorate)饮料

比如,顾客想要摩卡和奶泡深焙咖啡,那么:
取出一个深焙咖啡(DarkRoast)对象
以摩卡(Mocha)对象装饰它
以奶泡(Whip)对象装饰它
调用cost方法,并依赖委托(delegrate)将调料的价格加上去

步骤:

“装饰者模式”——特点

装饰者和被装饰对象具有相同的超类型
可以用一个或多个装饰者包装一个对象
由于装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装)的场合,都可以用装饰过的对象代替她
装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的
对象可以在任何时候被装饰,所以可以在运行时动态的、不限量的用需要的装饰者来装饰对象

装饰者模式类图关系

星巴兹咖啡销售系统装饰者模式类图关系

思考:为什么Decorate类扩展自Component类?

装饰者和被装饰者必须是一样的类型,我们在此使用继承达到“类型匹配”
类型匹配意味着装饰者和被装饰者具有相同的接口,从而装饰者可以取代被装饰者
新的行为并不是继承自超类,而是由组合对象得到,即所有饮料和调料可以更有弹性的加以混合和匹配
我们可以在任何时候,实现新的装饰者增加新的行为。如果依赖继承,每当需要新行为时,必须修改代码
Component类型可以使用抽象类,也可以使用接口

问题:如果有一张订单:“双倍摩卡豆浆奶泡拿铁咖啡”,应该如何进行设计?

这里代码的具体实现就不再写了,网上这样的代码太多了?

山西面馆中的“装饰者模式”

先上类图:直观形象

 代码实现:

  1. /// <summary>
  2. ///抽象类 食物基类 其他类都继承自该类
  3. /// </summary>
  4. public abstract class Food
  5. {
  6. protected string description = "未知的饭";
  7. public virtual string GetDescription()
  8. {
  9. return this.description;
  10. }
  11. public abstract double Cost();
  12. }
  1. /// <summary>
  2. /// 所有配料的基类 继承自Food类 保持类型一致
  3. /// </summary>
  4. public abstract class Ingredients : Food
  5. {
  6.  
  7. }

具体的配料:鸡蛋,西红柿类继承配料类

  1. public class Egg : Ingredients
  2. {
  3. Food meal;
  4. public Egg(Food meal)
  5. {
  6. this.meal = meal;
  7.  
  8. }
  9. public override double Cost()
  10. {
  11. return 3.0 + meal.Cost();
  12. }
  13.  
  14. public override string GetDescription()
  15. {
  16. return "鸡蛋"+meal.GetDescription();
  17. }
  18. }
  1. public class Tomato : Ingredients
  2. {
  3. Food meal;
  4. public Tomato(Food meal)
  5. {
  6. this.meal = meal;
  7.  
  8. }
  9. public override double Cost()
  10. {
  11. return 3.0 + meal.Cost();
  12. }
  13.  
  14. public override string GetDescription()
  15. {
  16. return "西红柿" + meal.GetDescription();
  17. }
  18. }

所谓的component类(Noodle)继承自Food类

  1. public class Noodle : Food
  2. {
  3. public Noodle()
  4. {
  5. description = "面";
  6. }
  7. public override double Cost()
  8. {
  9. return 3.0;
  10. }
  11. }

控制台测试程序:

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. //创建被装饰的对象 noodle
  6. Food f1 = new Noodle();
  7. //用鸡蛋装饰
  8. f1 = new Egg(f1);
  9. //用西红柿装饰
  10. f1 = new Tomato(f1);
  11. Console.WriteLine(f1.GetDescription() + "\t" + f1.Cost() + "¥");
  12. Console.Read();
  13. }
  14. }

结果:

思考:装饰者模式是否对修改封闭,对扩展开放呢?

那么,测试一下,加入现在我想要一份酱爆鸡丁盖浇饭,该怎么实现?

  1. public class Rice : Food
  2. {
  3. public Rice()
  4. {
  5. description = "盖浇饭";
  6. }
  7. public override double Cost()
  8. {
  9. return 5.0;
  10. }
  11. }
  1. public class Chicken : Ingredients
  2. {
  3. Food meal;
  4. public Chicken(Food meal)
  5. {
  6. this.meal = meal;
  7. }
  8. public override string GetDescription()
  9. {
  10. return "酱爆鸡丁" + meal.GetDescription();
  11. }
  12. public override double Cost()
  13. {
  14. return 9.0 + meal.Cost();
  15. }
  16. }
  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. //创建被装饰的对象 noodle
  6. Food f1 = new Noodle();
  7. //用鸡蛋装饰
  8. f1 = new Egg(f1);
  9. //用西红柿装饰
  10. f1 = new Tomato(f1);
  11. Console.WriteLine(f1.GetDescription() + "\t" + f1.Cost() + "¥");
  12. //创建被装饰的对象 米饭
  13. Food f2 = new Rice();
  14. //用酱爆鸡丁 装饰(将配料合并了)
  15. f2 = new Chicken(f2);
  16. Console.WriteLine(f2.GetDescription() + "\t" + f2.Cost() + "¥");
  17. Console.Read();
  18. }
  19. }

结果:

很容易扩展吧?只需要加两个类继承对应的类就可以了,原来的内部代码不需要修改,就可以实现

总结

在我们的代码中,应该允许行为遵循对扩展开放-对修改关闭的原则,这样就可以无需修改现有的代码就可以实现我们扩展的功能。

装饰者模式意味着一群装饰者类,这些类用来包装具体组件。

装饰者反映出被装饰者的组件类型(具有相同的类型)

装饰者可以在被装饰者的行为前面或后面加上自己的行为,甚至将被装饰者的行为整个取代,而达到特定的目的。

可以用无数个装饰者包装一个组件。

装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。

装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得复杂。

(最近一直在看设计模式,上班下班的路上想,吃饭的时候也想,不求精通,只求在项目中遇到了,能认出他,不求他认识我)

参考书:

Head First 设计模式

[Head First设计模式]山西面馆中的设计模式——装饰者模式的更多相关文章

  1. [Head First设计模式]山西面馆中的设计模式——观察者模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 引言 不知不自觉又将设计模式融入生活了,吃个饭也不得安生,也发现生活中的很多场景,都可以用设计模式来模拟.原来设计模式就在 ...

  2. [Head First设计模式]山西面馆中的设计模式——建造者模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 引言 将学习融入生活中,是件很happy的事情,不会感 ...

  3. [Head First设计模式]抢票中的设计模式——代理模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  4. [转载]Java中继承、装饰者模式和代理模式的区别

    [转载]Java中继承.装饰者模式和代理模式的区别 这是我在学Java Web时穿插学习Java设计模式的笔记 我就不转载原文了,直接指路好了: 装饰者模式和继承的区别: https://blog.c ...

  5. PHP 实战之设计模式:PHP 中的设计模式

    本文主要讨论下Web开发中,准确而言,是PHP开发中的相关的设计模式及其应用.有经验的开发者肯定对于设计模式非常熟悉,但是本文主要是针对那 些初级的开发者.首先我们要搞清楚到底什么是设计模式,设计模式 ...

  6. 设计模式在cocos2d-x中的使用--简单工厂模式(Simple Factory)

    什么是简单工厂模式? 从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式.通过专门定义一个类来负责创建其它类的实例,被创建的实例 ...

  7. 设计模式(十):Decorator装饰者模式 -- 结构型模式

    1. 概述 若你从事过面向对象开发,实现给一个类或对象增加行为,使用继承机制,这是所有面向对象语言的一个基本特性.如果已经存在的一个类缺少某些方法,或者须要给方法添加更多的功能(魅力),你也许会仅仅继 ...

  8. JAVA设计模式详解(三)----------装饰者模式

    今天LZ带给大家的是装饰者模式,提起这个设计模式,LZ心里一阵激动,这是LZ学习JAVA以来接触的第一个设计模式,也许也是各位接触的第一个设计模式.记得当初老师在讲IO的时候就提到过它:“是你还有你, ...

  9. 开源框架是如何使用设计模式的-MyBatis缓存机制之装饰者模式

    写在前面 聊一聊MyBatis是如何使用装饰者模式的,顺便回顾下缓存的相关知识,可以看看右侧目录一览内容概述. 装饰者模式 这里就不了它的概念了,总结下就是套娃.利用组合的方式将装饰器组合进来,增强共 ...

随机推荐

  1. STM32 Unicode 与 GBK 转换 .bin文件放到SD卡是啥意思

    2个数组 : }; }; 一个是Unicode 编码,一个是GBK编码: 用c2b软件转成.bin 二进制文件放到SD卡里: SD卡放入字库 .FON STM32 代码: 代码中SD卡字库和二进制路径 ...

  2. linux中的权限对于文件和目录的重要性

    对于文件 r 可以读取文件的实际内容 w 可以编辑文件的内容 x 文件可以被系统执行 对于目录 r 具有读取目录的结构列表,也就是说你可以用ls命令查看目录下的内容列表 w 可以建立新的文件,删除文件 ...

  3. [转]jquery遍历table的tr获取td的值

    html代码: 1 <tbody id="history_income_list"> 2 <tr> 3 <td align="center& ...

  4. WPF RichTextbox

    WPFTextBoxAutoComplete AvalonEdit WPF SyntaxHighlightBox   WinForm 下的 Fast Colored TextBox for Synta ...

  5. Label控件如何根据字符串自定义大小

    一.. this.label_Msg.AutoSize = false;  //设置label空件不能自动大小 二.. 用代码控制label控件的大小 1.根据字符串.label的宽度 计算字符串的面 ...

  6. PHP unset销毁变量并释放内存

    PHP的unset()函数用来清除.销毁变量,不用的变量,我们可以用unset()将它销毁.但是某些时候,用unset()却无法达到销毁变量占用的内存!我们先看一个例子: <?php $s=st ...

  7. Path Sum II

    Path Sum II Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals ...

  8. flex align-content中的描述的“多根轴线的对齐方式”中的“多根轴线”到底是什么

    flex 有两条轴线,根据flex-flow 设置的来判断的,水平为主轴的话,那么值为row,垂直为主轴的话那么为column: 其中设置align-items 和 align-content都是来设 ...

  9. 搭建一套自己实用的.net架构(4)【CodeBuilder-RazorEngine】

    工欲善其事必先利其器,  下面来说说代码生成器. 现在代码生成器品种繁多各式各样, 什么codesmith.T4. 动软也算.那么每款代码生成器都有自己模板解析引擎. 现在比较流行的 NVelocit ...

  10. FineUI(专业版)v1.2.0 和 FineUI(开源版)v4.1.1 同时发布!

    FineUI(开源版)v4.1.1 (建议所有 v4.x 升级到此版本):http://fineui.com/demo/ +2014-08-15 v4.1.1        -修正Form中表单字段设 ...