3. 装饰者模式:装饰对象
来源:互联网 发布:怎么申请开淘宝店 编辑:程序博客网 时间:2024/04/27 18:41
3.1. 引言: Es什么so?Cappu什么no?
你会不会忽然地出现,在街角的咖啡店……
喂喂,喝咖啡本来是为了抖擞精神,不要搞得这么伤感。
Espresso,Cappuccino,Latte……是不是傻傻分不清?如果你只喝美式咖啡(和现在的我一样),或者只喝速溶咖啡(和过去的我一样),或者有选择恐惧症,那么从喝咖啡的角度来说,接下来的内容可能跟你关系不大。但咱们不是来学习设计模式的嘛。
3.2. 新的需求:一名咖啡师的自我修养
作为喝咖啡的人,可以完全不去探究喝到嘴里的究竟是什么,只要味道好就可以了。但如果你要成为一名咖啡师,那可就要好好修炼了。我们先来看看常见的几种咖啡是怎么做出来的。
好像也没那么复杂,不同的咖啡就是成分不同而已。那么我们开始动手吧。
3.3. 解决方案
做一杯Coffee就是实例化一个Coffee对象,那么就需要一个Coffee类,它包含至少两个成员:Cost用来计算费用,Description用来描述成分。
3.3.1. 非常蠢的做法:Kaboom!
3.2.中列举了9种Coffee,那就建立9个ConcreteCoffee类吧。每新增加一种Coffee,就得新增加一个ConcreteCoffee类。甚至原有的Coffee需要一些成分比例的调整时,也得新增加一个ConcreteCoffee类。这样下去,类会多得爆炸的,Kaboom!
这违背了“针对接口编程,不针对实现编程”的OO原则。
再者,不同种类Coffee的组成也不是完全不同的,比如Espresso就出现在每一种Coffee中,它的Description和Cost属性完全应该被其他种类的Coffee复用。
这违背了“封装变化”的OO原则。
这样不仅很没有原则,而且真的是蠢哭了。
3.3.2. 一般蠢的做法:扩充超类的成员
为了增加复用性,一个方法就是扩充超类成员,把3.2.中每种咖啡成分的描述及费用都包含进来,子类(ConcreteCoffee类)继承超类之后根据实际情况设置属性即可。以Latte类为例(这里省略了对成分的描述)。
Coffee基类
public class Coffee { // 每种成分的费用 private const float espressoCost = 5; private const float milkFoamCost = 1; private const float steamedMilkCost = 3; // 是否需要某种成分 public bool HasEspresso { get; set; } public bool HasMilkFoam { get; set; } public bool HasSteamedMilk { get; set; } // Constructor,初始状态时不包含任何成分 public Coffee() { HasEspresso = false; HasMilkFoam = false; HasSteamedMilk = false; } // 根据成分计算总费用 public float Cost() { float totalCost = 0; if (HasEspresso) { totalCost += espressoCost; } if (HasMilkFoam) { totalCost += milkFoamCost; } if (HasSteamedMilk) { totalCost += steamedMilkCost; } return totalCost; } }
ConcreteCoffee类
public class Latte : Coffee{ public Latte() { this.HasEspresso = true; this.HasMilkFoam = true; this.HasSteamedMilk = true; }}
来做一杯Latte吧:
Coffee coffee = new Latte();Console.WriteLine(coffee.Cost());
3.4. 新的需求:更多的成分,更多的组合
3.3.2.中解决了3.3.1.中的问题,但这并不意味着就没有问题了,一名有理想的咖啡师总是要考虑得更多一点。
1. 大部分Coffee并不需要那么多成分,所以ConcreteCoffee类并不需要从Coffee基类中继承所有的成员。你还记得1.3.1.中那个鸭子满天飞的世界吗?这里也违背了“多用组合,少用继承”的OO原则。
2. Coffee成分的种类、费用、比例都有可能发生变化(新的Coffee调配方法,原料价格变动,不同顾客的口味偏好),按照现有的结构,Coffee基类需要一直被修改。变化的部分存在于超类中,这违背了“封装变化”的OO原则。
3.5. 解决方案:多用组合,少用继承
如何改进呢?别忘了,还有一个“多用组合,少用继承”的OO原则。
3.5.1. 成分
不同ConcreteCoffee对象的各种成分有着一些共同的特征,比如费用,比如对成分的描述。我们可以建立一个AbstractComponent类,每种具体成分都作为一个ConcreteComponent类去继承它。
AbstractComponent类
public abstract class CoffeeComponent{ public float Cost { get; set; } public string Description { get; set; } public float Quantity { get; set; } public void Add(CoffeeComponent coffeeComponent) { this.Cost += coffeeComponent.Cost; this.Description = string.Format("{0} + {1}", this.Description, coffeeComponent.Description); } public void GetDescription() { Console.WriteLine("Total cost: " + this.Cost); Console.WriteLine("This coffee consists of: " + this.Description); Console.WriteLine(); }}
ConcreteComponent类
public class Espresso : CoffeeComponent{ private const float cost = 5; public Espresso(float quantity) { this.Quantity = quantity; this.Cost = cost * this.Quantity; this.Description = this.Quantity + " Espresso"; }}
3.5.2. 由成分组成的咖啡
对于标准化的Coffee,可以建立由ConcreteComponent对象组成的ConcreteCoffee类。每个ConcreteCoffee对象都是由若干ConcreteComponent对象组成的,每个ConcreteComponent对象就像是一个装饰者(Decorator)一样去装饰其他的ConcreteComponent对象,它本身也可以被其他的ConcreteComponent对象所装饰。
建立一个继承自CoffeeComponent类的AbstractCoffee类,该类具有一个CoffeeComponent对象成员,该成员由若干CoffeeComponent对象组成。所有的ConcreteCoffee类都继承自AbstractCoffee类。
AbstractCoffee类:
public abstract class AbstractCoffee : CoffeeComponent{ public CoffeeComponent Coffee { get; set; }}
ConcreteCoffee类:
public class Cappuccino : AbstractCoffee{ public Cappuccino() { this.Description = "Cappuccino"; this.Coffee = new Espresso((float)1); this.Coffee.Add(new SteamedMilk((float)0.5)); this.Coffee.Add(new MilkFoam((float)1)); }}
对于定制化的Coffee,既可以单独建立一个 ConcreteCoffee类,也可以在运行时决定添加哪些ConcreteComponent对象。
与3.3.中的方法对比:
以前的做法
现在的做法
先获取所有的成分,再看需要什么
需要哪种成分就单独获取这种成分
利用继承,子类的行为是在编译时静态决定的
利用组合,子类的行为可以在运行时动态地扩展
3.6. 最终方案
3.6.1. UML
其中,Espresso,MilkFoam,SteamedMilk为ConcreteComponent类,继承自CoffeeComponent类。Cappuccino,Latte为ConcreteCoffee类,继承自AbstractCoffee类。
3.6.2. 测试代码
class Program{ static void Main(string[] args) { // 标准化Coffee Console.WriteLine("标准化Coffee:"); // Cappuccino Coffee standardCoffee = new Cappuccino(); Console.WriteLine(string.Format("Make a cup of {0}.", standardCoffee.Description)); standardCoffee = ((Cappuccino)standardCoffee).Coffee; standardCoffee.GetDescription(); // 运行时添加成分 Console.WriteLine("运行时添加成分:"); Console.WriteLine("Add more steamed milk."); standardCoffee.Add(new SteamedMilk((float)5)); standardCoffee.GetDescription(); // 定制化Coffee Console.WriteLine("定制化Coffee:"); Console.WriteLine("Make a cup of cutomized coffee."); Coffee customizedCoffee = new Espresso((float)1); customizedCoffee.Add(new MilkFoam((float)0.5)); customizedCoffee.Add(new SteamedMilk((float)0.2)); customizedCoffee.GetDescription(); }}
3.6.3. 运行结果
标准化Coffee:
Make a cup of Cappuccino.
Total cost: 8.5
This coffee consists of: 1 Espresso + 0.5 Steamed Milk + 1 Milk Foam
运行时添加成分:
Add more steamed milk.
Total cost: 23.5
This coffee consists of: 1 Espresso + 0.5 Steamed Milk + 1 Milk Foam + 5 Steamed Milk
定制化Coffee:
Make a cup of cutomized coffee.
Total cost: 6.6
This coffee consists of: 1 Espresso + 0.5 Milk Foam + 0.2 Steamed Milk
3.7. 总结
【装饰者模式】动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
包含的OO原则:
类应该对扩展开放,对修改关闭。对修改关闭指的是不修改现有的代码,尤其是不修改超类的代码。
(注:本文为笔者学习《Head First Design Patterns》的笔记,若涉及版权等问题,请告知。)
- 3. 装饰者模式:装饰对象
- 3.装饰者模式
- 3. 装饰者模式
- 用装饰模式装饰 Servlet Request 对象
- Decorator 装饰对象结构模式
- 设计模式--3装饰对象
- 装饰者对象
- 装饰者模式-装饰java.io类
- 【设计模式】3. 装饰者模式
- 装饰模式-撤销装饰
- 装饰者模式(Derector)
- 装饰者模式
- Decorator 装饰者模式
- 装饰者设计模式
- 装饰者模式
- 装饰者模式
- 装饰者模式
- 装饰者模式(Decorator)
- [ERP]ERP原理与应用试题(附答案)
- linux命令 sort
- Code forces Round #510 div2
- EditPlus绿色汉化破解版的注册码信息
- java sql ResultSet 如何判断ResultSet中存在记录
- 3. 装饰者模式:装饰对象
- JSON
- ListView中Item多布局
- 标题文字跑马灯效果实现
- SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <1MB model size阅读笔记
- pdksh-5.2.14 elfutils-libelf-devel-0.97 Are Missing
- Java基础知识(二)
- cocos2dx源码分析:异步任务线程池AsyncTaskPool
- Spring同Mybatis的整合