前言:
本节将深度讨论继承滥用问题。将会学到使用对象组合的方式,在执行时装饰类。在不改动不论什么底层代码的情况下。给对象赋予新的职责。
1. 基本需求:咖啡连锁店业务扩张须要又一次设计订单系统
背景:由于StarBuzz咖啡连锁店业务扩张。准备更新订单系统。以合乎他们的饮料供应要求。
他们原来的类设计例如以下:
用户在购买咖啡的时候,能够能会要求在咖啡中增加各种调料,StarBuzz会依据用户增加的不同调收取不同费用,新的订单系统必须考虑到这些调料部分。
1.1 第一次设计
以上的每个类的Cost()方法将会算出咖啡加上订单的各种调料的价钱。
尽管能够满足需求,可是这样会须要非常多非常多的类,并且也违反了OO的设计原则。
1.2 第二次设计
不须要创建那么多的类。仅仅须要通过利用实例变量和继承。就能够追踪调料。
设计例如以下:
这样做,确实能够临时满足需求,可是还会存在一些潜在的隐患,例如以下:
l 调料价格变动会改变原有代码
l 新增调料,除了加上新增方法外,还须要改变超类中的Cost()方法
l 依赖继承。子类将继承一些对自身并不合适的方法
l 部分需求无法满足:如双倍摩卡咖啡
l 违反了开放—关闭原则
2. 引入装饰者模式
2.1 开发----关闭原则
在上一节的第二次设计中我们能够看出这样的设计方法明显的违背了“开发—关闭”原则,那什么是开闭原则呢?定义例如以下:
开发—关闭原则:类应该是对扩展开放,对改动关闭。
我们的目标是同意类easy扩展,在不改动现有代码的情况下。就可搭配新的行为。这样的设计具有弹性能够应对改变,能够接受新的功能来应对改变的需求。遵循开开放—关闭原则,一般会引入新的抽象层次,增加代码的复杂度,须要选择在设计中最可能改变的地方,然后应用开发-关闭原则。
2.2 认识装饰者模式,并以装饰者构造饮料订单
在1中我们已经了解到通过继承无法全然解决这个问题。这里我们以饮料为主体,然后在执行时以调料来“装饰”饮料。
比如:假设客户想要摩卡和奶泡深焙咖啡。例如以下:
l 拿一个深焙(DarkRoast)对象
l 以摩卡(Mocha)对象装饰它
l 以奶泡(Whip)对象装饰它
l 调用Cost()方法,并依赖托付(delegate)将调料的价钱加上去
说明:以DarkRoast对象開始,顾客想要摩卡(Mocha),所以建立一个Mocha对象,用它将DarkRoast对象包起来,顾客想要奶炮(Whip),所以须要建立一个Whip装饰者。并用它将Mocha对象包起来。最后算钱。通过调用最外层的装饰者Whip的Cost()方法就能够办到。其调用步骤例如以下:
Whip.Cost()àMocha.Cost()àDarkRoast.Cost()(返回DarkRoast的价钱)àMocha.Cost()(Mocha在DarkRoast的结果上加上自己的价钱)àWhip.Cost()(Whip在Mocha的返回结果上加上自己的价钱)
基于以上的这样的分析,我们能够得出下面的结论:
装饰者和被装饰对象有同样的超类型。
能够用一个或者多个装饰者包装一个对象。
由于装饰者和被装饰者有同样的超类型。所以在不论什么须要原始对象的场合。能够用装饰过的对象代替它。
装饰者能够在所托付被装饰者的行为之前或者之后。加上自己的行为,达到特定目的。
对象能够在不论什么时候被装饰,所以能够在执行时动态的用你喜欢的装饰者来装饰对象。
2.3 定义装饰者模式
装饰者模式:
动态的将责任附加到对象上,若要扩展功能,装饰者提供了更有弹性的替代方案。
设计类图例如以下:
依据这样的装饰者模式的类图,我们设计出StarBuzz的类图,也让它符合这样的结构设计,类图例如以下:
装饰者和被装饰者都继承自Beverage类。也就是拥有共同的超类,这里,我们是利用继承达到“类型匹配”,而不是利用继承获得“行为”。
装饰者和组件组合时,就是在增加新的行为。所得的新行为并非继承自超类。而是由组合对象得来的。
假设依赖继承。那么类的行为仅仅能在编译时静态决定,行为不是来自超类就是子类覆盖后的版本号,每当须要新行为时。还得改动现有代码。假设利用组合,就能够动态的实现新的装饰者增加新的行为。
3 用装饰者模式实现咖啡店需求
依据在2.3中设计的咖啡店的类图。下面就进行详细的编码实现:
3.1 Beverage类(抽象组件)
/// Description: Beverage抽象类 /// </summary> public abstract class Beverage { public string description = "UnknownBeverage"; public abstract stringGetDescription(); public abstract double Cost(); }
3.2Condiment(调料)基类(继承自Beverage基类,抽象装饰者)
/// Description:调料基类、派生类 /// </summary> public abstract class CondimentDecorator:Beverage { //public abstract string GetDescription(); }
3.3饮料类(继承Beverage基类,详细组件)
public class Espresso:Beverage { public Espresso() { description = "Espresso";//设置饮料的表述,description继承自Beverage类的实例变量 } public override double Cost() { return 1.99; } public override string GetDescription() { return description; } } public class HouseBlend : Beverage { public HouseBlend() { description = "HouseBlend"; } public override double Cost() { return 0.89; } public override string GetDescription() { return description; } } public class DarkRoase : Beverage { public DarkRoase() { description = "DarkRoase"; } public override double Cost() { return 1.11; } public override string GetDescription() { return description; } } public class Decat : Beverage { public Decat() { description = "Decat"; } public override double Cost() { return 1.22; } public override string GetDescription() { return description; } }
3.4调料类(装饰者)
public class Mocha : CondimentDecorator { Beverage beverage; public Mocha(Beverage beverage) { this.beverage = beverage; } public override string GetDescription() { return beverage.GetDescription() + ",Mocha"; } public override double Cost() { return 0.2 + beverage.Cost(); } } public class Soy : CondimentDecorator { Beveragebeverage; public Soy(Beverage beverage) { this.beverage = beverage; } public override string GetDescription() { return beverage.GetDescription() + ",Soy"; } public override double Cost() { return 0.3 + beverage.Cost(); } } public class Whip : CondimentDecorator { Beverage beverage; public Whip(Beverage beverage) { this.beverage = beverage; } public override string GetDescription() { return beverage.GetDescription() + ",Whip"; } public override double Cost() { return 0.3 + beverage.Cost(); } }
3.5 測试
Beverage.Beverage beverage = newBeverage.Espresso(); Console.WriteLine(beverage.GetDescription()+ " $ " + beverage.Cost()); Beverage.Beverage beverage1 = newBeverage.DarkRoase(); beverage1 = new Beverage.Mocha(beverage1); beverage1 = new Beverage.Mocha(beverage1); beverage1 = new Beverage.Whip(beverage1); Console.WriteLine(beverage1.GetDescription()+ " $ " + beverage1.Cost()); Beverage.Beverage beverage2 = newBeverage.HouseBlend(); beverage2 = new Beverage.Soy(beverage2); beverage2 = new Beverage.Mocha(beverage2); beverage2 = new Beverage.Whip(beverage2); Console.WriteLine(beverage2.GetDescription()+ " $ " + beverage2.Cost());
结果例如以下图:
4 总结
通过本章的学习,我们能够学到下面知识:
l OO原则:
封装变化
多用组合,少用继承
针对接口编程。不针对实现编程
为交互对象之间的松耦合设计而努力
对扩展开放,对改动关闭(本章节新学习的OO原则)
l OO模式
装饰者模式—动态地将责任附加到对象上,想要扩展功能,装饰者提供有别于继承的还有一种选择。
l 要点归纳
继承和装饰者都能够让我们扩展行为,但继承不是弹性设计的最佳方案。
装饰者模式意味着一群装饰者类。装饰者类反应了被装饰组件的类型。能够用多个装饰者包装对象。
装饰者能够在被装饰者的行为之前或者之后加上自己的行为。甚至将被装饰者的行为代替。以到达特定目的。
装饰者模式会导致设计中出现很多小对象。过度使用会使程序变得复杂。