原文链接:http://blog.csdn.net/zhshulin/article/details/38665187
1、概念
装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
但是纯粹的装饰模式很难找到,大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。半透明的装饰模式是介于装饰模式和适配器模式之间的。适配器模式的用意是改变所考虑的类的接口,也可以通过改写一个或几个方法,或增加新的方法来增强或改变所考虑的类的功能。
大多数的装饰模式实际上是半透明的装饰模式,这样的装饰模式也称做半装饰、半适配器模式。
2、针对的问题
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。不改变接口的前提下,增强所考虑的类的性能。
何时使用:
1)需要扩展一个类的功能,或给一个类增加附加责任。
2)需要动态的给一个对象增加功能,这些功能可以再动态地撤销。
3)需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使继承变得 不现实。
3、角色组成
l 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
l 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类
l 装饰角色(Decorator):持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口
l 具体装饰角色(ConcreteDecorator):负责给构件对象“贴上”附加的责任
4、举例说明
咖啡是一种饮料,咖啡的本质是咖啡豆+水磨出来的。咖啡店现在要卖各种口味的咖啡,如果不使用装饰模式,那么在销售系统中,各种不一样的咖啡都要产生一个类,如果有4中咖啡豆,5种口味,那么将要产生至少20个类(不包括混合口味),非常麻烦。使用了装饰模式,只需要11个类即可生产任意口味咖啡(包括混合口味)。
4.1、类图
4.2、源码
/// <summary>
/// 饮料接口(Component)
/// </summary>
interface Beverage
{
string GetDescription();
double GetPrice();
}
/// <summary>
/// 第一种咖啡豆
/// 实现于接口Beverage(ConcreteComponent)
/// </summary>
class CoffeeBean1 : Beverage
{
private string myDescription = "第一种咖啡豆 ";
private double price = 35;
public string GetDescription()
{
return myDescription;
}
public double GetPrice()
{
return price;
}
}
/// <summary>
/// 第二种咖啡豆
/// 实现于接口Beverage(ConcreteComponent)
/// </summary>
class CoffeeBean2 : Beverage
{
private string myDescription = "第二种咖啡豆 ";
private double price = 44;
public string GetDescription()
{
return myDescription;
}
public double GetPrice()
{
return price;
}
}
/// <summary>
/// 修饰类
/// </summary>
class Decorator : Beverage
{
private Beverage beverage;
public void SetBeverage(Beverage be)
{
beverage = be;
}
public virtual string GetDescription()
{
return beverage.GetDescription();
}
public virtual double GetPrice()
{
return beverage.GetPrice();
}
}
/// <summary>
/// 继承于Decorator(ConcreteDecorator)
/// </summary>
class Milk : Decorator
{
private string description = "牛奶 ";
private double price = 5;
public override string GetDescription()
{
return base.GetDescription() + description;
}
public override double GetPrice()
{
return base.GetPrice() + price;
}
}
/// <summary>
/// 继承于Decorator(ConcreteDecorator)
/// </summary>
class Mocha : Decorator
{
private string description = "摩卡 ";
private double price = 8;
public override string GetDescription()
{
return base.GetDescription() + description;
}
public override double GetPrice()
{
return base.GetPrice() + price;
}
}
主函数进行测试
static void Main(string[] args)
{
Decorator drink = new Decorator();
CoffeeBean1 bean1 = new CoffeeBean1();
CoffeeBean2 bean2 = new CoffeeBean2();
Mocha mocha = new Mocha();
Milk milk = new Milk();
drink.SetBeverage(bean1);
milk.SetBeverage(drink);
Console.WriteLine("选择的饮料:{0}\n价格为:{1}", milk.GetDescription(), milk.GetPrice());
drink.SetBeverage(bean2);
mocha.SetBeverage(drink);
Console.WriteLine("选择的饮料:{0}\n价格为:{1}", mocha.GetDescription(), mocha.GetPrice());
}
运行结果:
5、装饰模式和适配器模式的关系
装饰模式和适配器模式都是“包装模式(Wrapper Pattern)”,它们都是通过封装其他对象达到设计的目的的,但是它们的形态有很大区别。
理想的装饰模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合。
装饰模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。透明的装饰模式也就是理想的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。相反,如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰模式也是可以接受的,称为“半透明”的装饰模式,如下图所示。
在适配器模式里面,适配器类的接口通常会与目标类的接口重叠,但往往并不完全相同。换言之,适配器类的接口会比被装饰的目标类接口宽。
显然,半透明的装饰模式实际上就是处于适配器模式与装饰模式之间的灰色地带。如果将装饰模式与适配器模式合并成为一个“包装模式”的话,那么半透明的装饰模式倒可以成为这种合并后的“包装模式”的代表。
6、优点
(1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除 掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
(2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
7、缺点
使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。