装饰者模式

一、基本概述

问题:有咖啡店,卖多种咖啡,以及调料(如豆浆、牛奶、奶油等)。现有的订单系统的类结构如下。

二、分析说明

上面的设计方式存在的一些问题?

  1. 调料价钱的改变会使我们更改现有代码。
  2. 一旦出现新的调料,我们就需要加上新的方法(属性),并改变超类中的Cost()方法。
  3. 以后可能会开发出新饮料,对这些饮料而言(例如,茶),某些调料可能并不适合,但是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法。例如,HasWhip(加奶泡)。
  4. 若是需要双倍摩卡咖啡,怎么办。
  5. 违反了OO原则,如针对接口编程,而非针对实现编程。多用组合,少用继承。

小结:

1.尽管继承的威力强大,但是它并不总是能够实现最有效弹性和最好维护的设计。

2.利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。

3.我们可以利用此技巧把多个新职责,甚至是设计超类时还没有想到的职责附加在对象上,而且可以不用修改原来的代码。

通过动态地组合对象,可以写新的代码添加新功能,而无需修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。

 

三、如何解决

1.OO原则:类应该对扩展开放,对修改关闭。(开放—关闭原则)

补充:

  那么我们是否需要系统对设计的每个部分都遵循开放—关闭原则呢?

  答案是否定的。通常你办不到,要让OO设计同时具备开放性与关闭性,又不修改现有的代码,需要花费许多时间和努力。一般来说,我们实在没有闲工夫把设计的每个部分都这么设计(而且,就算做得到,也可能是一种浪费)。遵循开放—关闭原则,通常会引入新的抽象层次,增加代码的复杂度,你需要把注意力集中在设计中最有可能改变的地方,然后应用开放—关闭原则。

  恰恰装饰者模式符合这一原则。

2.装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

补充:

(1)  装饰者和被装饰者对象有相同的超类型。

(2)  你可以用一个或多个装饰者包装一个对象。

(3)  既然装饰者和被装饰者对象有相同的超类型,所以在任何需要原始对象(被包装对象)的场合,可以用装饰者对象代替它。

(4)  装饰者可以在所委托的被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目地。

(5)  对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用装饰者来装饰对象。

 

  上面虽然说明了装饰者模式的“角色”,但是没有说明在具体的实际中怎么使用,下面的类图结构能够帮我们梳理思路,后面的饮料问题就是套用此结构图。

看了上面的类图,是否在继承和组合之间思维有些混淆。

(1)  如CondimentDecorator扩展自Beverage类,这用到了继承,不是吗?

  答:的确是如此,但我认为这么做的重点在于,装饰者和被装饰者必须是一样的类型。也就是有共同的超类,这是相当关键的地方。在这里,我们利用继承到达“类型匹配”,而不是利用继承获得“行为”。

(2)  既然装饰者必须能代替被装饰者,那行为又从哪里来呢?

  答:当我们将装饰者与组件组合时,就是在加入新的行为,所得到的新行为,并不是继承自超类,而是由组合对象得来的。

(3)  如果我们需要继承的是Component类型,为什么不把Beverage类设计成一个接口,而是设计成一个抽象类呢?

  答;通常装饰者模式是采用抽象类,但是也可以使用接口。尽管如此,但我们都努力避免修改现有的代码。而这个程序的Beverage类本身一个抽象类。

 

下面列出饮料系统的详细的类图与代码。

/// <summary>
/// 饮料
/// </summary>
public abstract class Beverage
{
    protected string description = "Unknown Beverage";
    public string Description
    {
        get { return description; }
    }

    public abstract float Cost();
}
/// <summary>
/// 抽象的调料装饰者
/// </summary>
public abstract class CondimentDecorator : Beverage
{
    //TODO 可根据需要添加处理信息
}
public class Espresso : Beverage
{
    public Espresso()
    {
        description = "Espresso";
    }

    public override float Cost()
    {
        return 1.99f;
    }
}
public class HouseBlend : Beverage
{
    public HouseBlend()
    {
        description = "House Blend Coffee";
    }

    public override float Cost()
    {
        return 0.89f;
    }
}
public class DarkRoast : Beverage
{
    public DarkRoast()
    {
        description = "Dark Roast Coffee";
    }

    public override float Cost()
    {
        return 0.99f;
    }
}
public class Decaf : Beverage
{
    public Decaf()
    {
        description = "Decaf coffee";
    }

    public override float Cost()
    {
        return 1.05f;
    }
}
public class Mocha : CondimentDecorator
{
    private Beverage beverage;
    public Mocha(Beverage beverage)
    {
        this.beverage = beverage;
        this.description= beverage.Description + ",Mocha";
    }

    public override float Cost()
    {
        return 0.20f + beverage.Cost();
    }
}
public class Soy : CondimentDecorator
{
    private Beverage beverage;
    public Soy(Beverage beverage)
    {
        this.beverage = beverage;
        this.description = beverage.Description + ",Soy";
    }

    public override float Cost()
    {
        return 0.15f + beverage.Cost();
    }
}
public class Whip : CondimentDecorator
{
    private Beverage beverage;

    public Whip(Beverage beverage)
    {
        this.beverage = beverage;
        this.description= beverage.Description + ",Whip";
    }

    public override float Cost()
    {
        return 0.10f + beverage.Cost();
    }
}
[Test]
public void StarbuzzCoffee()
{
    Beverage beverage = new Espresso();
    Console.WriteLine("{0} ${1}", beverage.Description, beverage.Cost());

    Beverage beverage2 = new DarkRoast();
    beverage2 = new Mocha(beverage2);
    beverage2 = new Mocha(beverage2);
    beverage2 = new Whip(beverage2);
    Console.WriteLine("{0} ${1}", beverage2.Description, beverage2.Cost());

    Beverage beverage3 = new HouseBlend();
    beverage3 = new Soy(beverage3);
    beverage3 = new Mocha(beverage3);
    beverage3 = new Whip(beverage3);
    Console.WriteLine("{0} ${1}", beverage3.Description, beverage3.Cost());
}
View Code

------------------------以上内容根据《Head First Design mode》进行整理

posted @ 2017-03-02 09:26  殇曲?  阅读(321)  评论(0编辑  收藏  举报