在软件开发中,经常会遇到需要对对象动态添加额外功能的情况。例如,可能需要创建一个流对象来处理数据,但在某些情况下,希望这个流对象能够加密数据。在这种情况下,可以创建一个基础的流对象,并在需要时动态添加加密功能。
有人可能会问,为什么不将加密逻辑直接放入流类中,并通过布尔属性来控制其开关呢?这种方法确实可行,但如果需要添加多种自定义的加密逻辑,那么就需要通过继承现有类并在派生类中添加自定义的加密逻辑来实现。这种方法在加密是唯一需要的功能时是有效的,但如果需要动态添加多种功能,并且这些功能之间还可以组合使用,那么继承方式就不太适用了。这时,装饰者模式就显得非常有用。
装饰者模式允许动态地为对象添加额外的责任。它提供了一种灵活的替代继承的方法来扩展功能。
在深入了解装饰者模式的细节之前,先来看一下这个模式的类图,了解每个类的作用。
Component:定义了需要动态添加功能的接口。
ConcreteComponent:实际的对象,可以在其上动态添加功能。
Decorator:定义了可以添加到ConcreteComponent的所有动态功能的接口。
ConcreteDecorator:可以添加到ConcreteComponent的具体功能。每个需要的功能都将是一个ConcreteDecorator类。
为了更好地理解装饰者模式,来看一个烘焙店的计费系统的例子。这家烘焙店专门制作蛋糕和糕点。顾客可以购买蛋糕和糕点,然后选择在基础产品上添加额外的东西。额外的产品包括奶油、樱桃、香味和名片。如果采用传统的继承方法来创建这个计费系统,将得到诸如CakeOnly、CakeWithCreamAndCherry、CakeWithCreamAndCherryAndScent、CakeWithCreamAndCherryAndScentAndNameCard等类。这种方法不仅在开发上存在问题,而且在维护上也是一个噩梦。创建和维护这些类集将是一个非常大的挑战。
现在,让看看如何使用装饰者模式优雅地设计这个解决方案。首先,创建Component接口。
public abstract class BakeryComponent
{
public abstract string GetName();
public abstract double GetPrice();
}
这个类定义了需要动态添加功能的接口。现在来创建ConcreteComponent类。
class CakeBase : BakeryComponent
{
private string m_Name = "Cake Base";
private double m_Price = 200.0;
public override string GetName()
{
return m_Name;
}
public override double GetPrice()
{
return m_Price;
}
}
class PastryBase : BakeryComponent
{
private string m_Name = "Pastry Base";
private double m_Price = 20.0;
public override string GetName()
{
return m_Name;
}
public override double GetPrice()
{
return m_Price;
}
}
现在已经准备好了基础对象。接下来,来看看如何动态地添加其他所需的功能。让从Decorator类开始。
public abstract class Decorator : BakeryComponent
{
BakeryComponent m_BaseComponent = null;
protected string m_Name = "Undefined Decorator";
protected double m_Price = 0.0;
protected Decorator(BakeryComponent baseComponent)
{
m_BaseComponent = baseComponent;
}
#region BakeryComponent Members
string BakeryComponent.GetName()
{
return string.Format("{0}, {1}", m_BaseComponent.GetName(), m_Name);
}
double BakeryComponent.GetPrice()
{
return m_Price + m_BaseComponent.GetPrice();
}
#endregion
}
这里有两个需要注意的地方。首先,这个类实现了BakeryComponent接口。原因是一个带有组件的蛋糕也是一个蛋糕,因此所有可能在蛋糕上进行的操作也应该在装饰过的蛋糕上进行。其次,它还包含了一个BakeryComponent对象。原因是需要在蛋糕和装饰物之间建立逻辑上的is-a关系,但实际上并非如此,所以在内部持有一个BakeryComponent对象,以模仿这种is-a关系。
简而言之,所做的是,通过使用组合而不是继承,建立了一个动态的is-a关系,而不是静态的is-a关系。
现在,让看看如何实现ConcreteDecorators。
class ArtificialScentDecorator : Decorator
{
public ArtificialScentDecorator(BakeryComponent baseComponent) : base(baseComponent)
{
this.m_Name = "Artificial Scent";
this.m_Price = 3.0;
}
}
class CherryDecorator : Decorator
{
public CherryDecorator(BakeryComponent baseComponent) : base(baseComponent)
{
this.m_Name = "Cherry";
this.m_Price = 2.0;
}
}
class CreamDecorator : Decorator
{
public CreamDecorator(BakeryComponent baseComponent) : base(baseComponent)
{
this.m_Name = "Cream";
this.m_Price = 1.0;
}
}
在这些类中,简单地设置了装饰物的特定值,而没有定制任何行为。但如果愿意,甚至可以在ConcreteDecorator对象中定制行为或添加更多的状态变量。为了说明这一点,假设每当顾客选择在蛋糕上添加名片时,他就有资格获得下次购买的折扣卡,需要在收据上显示这个消息。让看看ConcreteDecorator是如何添加自己的状态和行为的。
class NameCardDecorator : Decorator
{
private int m_DiscountRate = 5;
public NameCardDecorator(BakeryComponent baseComponent) : base(baseComponent)
{
this.m_Name = "Name Card";
this.m_Price = 4.0;
}
public override string GetName()
{
return base.GetName() + string.Format("\n(Please Collect your discount card for {0}%)", m_DiscountRate);
}
}
现在,客户端应用程序可以创建这些ConcreteComponents与任何Decorator的组合。让看看客户端的示例代码实现。
static void Main(string[] args)
{
// Let us create a Simple Cake Base first
CakeBase cBase = new CakeBase();
PrintProductDetails(cBase);
// Lets add cream to the cake
CreamDecorator creamCake = new CreamDecorator(cBase);
PrintProductDetails(creamCake);
// Let now add a Cherry on it
CherryDecorator cherryCake = new CherryDecorator(creamCake);
PrintProductDetails(cherryCake);
// Lets now add Scent to it
ArtificialScentDecorator scentedCake = new ArtificialScentDecorator(cherryCake);
PrintProductDetails(scentedCake);
// Finally add a Name card on the cake
NameCardDecorator nameCardOnCake = new NameCardDecorator(scentedCake);
PrintProductDetails(nameCardOnCake);
// Lets now create a simple Pastry
PastryBase pastry = new PastryBase();
PrintProductDetails(pastry);
// Lets just add cream and cherry only on the pastry
CreamDecorator creamPastry = new CreamDecorator(pastry);
CherryDecorator cherryPastry = new CherryDecorator(creamPastry);
PrintProductDetails(cherryPastry);
}