2

Head First设计模式之装饰者模式(Decorator Pattern) - 星星之火116

 2 years ago
source link: https://www.cnblogs.com/Olive116/p/5276588.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

     本节将深度讨论继承滥用问题,将会学到使用对象组合的方式,在运行时装饰类,在不修改任何底层代码的情况下,给对象赋予新的职责。

1.    基本需求:咖啡连锁店业务扩张需要重新设计订单系统

背景:由于StarBuzz咖啡连锁店业务扩张,准备更新订单系统,以合乎他们的饮料供应要求。

他们原来的类设计如下:

用户在购买咖啡的时候,可以能会要求在咖啡中加入各种调料,StarBuzz会根据用户加入的不同调收取不同费用,新的订单系统必须考虑到这些调料部分。

1.1 第一次设计

以上的每一个类的Cost()方法将会算出咖啡加上订单的各种调料的价钱。虽然可以满足需求,但是这样会需要很多很多的类,而且也违反了OO的设计原则。

1.2 第二次设计

不需要创建那么多的类,只需要通过利用实例变量和继承,就可以追踪调料。

设计如下:

这样做,确实可以暂时满足需求,但是还会存在一些潜在的隐患,如下:

l  调料价格变动会改变原有代码

l  新增调料,除了加上新增方法外,还需要改变超类中的Cost()方法

l  依赖继承,子类将继承一些对自身并不合适的方法

l  部分需求无法满足:如双倍摩卡咖啡

l  违反了开放—关闭原则

2.    引入装饰者模式

2.1 开发----关闭原则

在上一节的第二次设计中我们可以看出这种设计方法明显的违背了“开发—关闭”原则,那什么是开闭原则呢?定义如下:

开发—关闭原则:类应该是对扩展开放,对修改关闭。

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为,这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。遵循开开放—关闭原则,通常会引入新的抽象层次,增加代码的复杂度,需要选择在设计中最可能改变的地方,然后应用开发-关闭原则。

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 = "Unknown Beverage";
public abstract string GetDescription();
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
{
Beverage beverage;
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 = new Beverage.Espresso();
Console.WriteLine(beverage.GetDescription() + " $ " + beverage.Cost());
Beverage.Beverage beverage1 = new Beverage.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 = new Beverage.HouseBlend();
beverage2 = new Beverage.Soy(beverage2);
beverage2 = new Beverage.Mocha(beverage2);
beverage2 = new Beverage.Whip(beverage2);
Console.WriteLine(beverage2.GetDescription() + " $ " + beverage2.Cost());

结果如下图:

通过本章的学习,我们可以学到以下知识:

l  OO原则:

封装变化

多用组合,少用继承

针对接口编程,不针对实现编程

为交互对象之间的松耦合设计而努力

对扩展开放,对修改关闭(本章节新学习的OO原则)

l  OO模式

装饰者模式—动态地将责任附加到对象上,想要扩展功能,装饰者提供有别于继承的另一种选择。

l  要点归纳

继承和装饰者都可以让我们扩展行为,但继承不是弹性设计的最佳方案。

装饰者模式意味着一群装饰者类,装饰者类反应了被装饰组件的类型,可以用多个装饰者包装对象。

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK