首页 > 技术文章 > C#设计模式之4:装饰者模式

pangjianxin 2017-12-04 19:53 原文

装饰者模式

背景是有一家星巴兹咖啡店,由于客源充足,所以决定重新设计他们的收费系统,以前的收费系统中只定义了一个表示饮料的Beverage的基类,它里面定义了一个Cost的方法用来计算饮料的花费,但是对于星巴兹来说他们的饮料的种类实在太多了,不能就每一种饮料就建立一个子类,类型爆炸!

 

所以要进行一番设计,来改变目前这种类型爆炸的局面。

利用继承设计子类的行为,实在编译时静态决定的,而且所有的子类都会继承到相同的行为,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态的进行扩展。通过动态的组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或者产生意外副作用的机会就会变得少很多。

设计原则:类应该对扩展开放,对修改关闭(开闭原则)

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为,如果实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。

遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。需要把注意力集中在设计中最有可能改变的地方,然后应用开放-关闭原则。

按照上述原则分析需求,首先,我们把饮料当作主体,而各种调料当作装饰,用调料来装饰饮料,如果顾客想要摩卡和奶泡咖啡,那么要做的是:

①准备一个咖啡对象。

②用摩卡装饰他。

③用奶泡装饰他。

④调用cost方法。

在给出具体的做法之前,先给出装饰者模式的定义:动态的将责任附加到对象上,如要扩展功能,装饰者提供了比继承更有弹性的替代方案。

简单的来说,就是用Decorator来装饰ConcreteComponent,因为他们都具有相同的基类,达到了一种“类型匹配”的目的,而不是用来获取基类的行为。到时候,相关的类型之间能够被互相取代。

当我们将装饰着和组件组合时,就是在加入新的行为,所得到的新的行为,并不是继承得来的,而是有组合对象得来的。如果依赖继承,那么类的行为就是在编译时静态决定的。换句话说,行为如果不是来自基类,就是来自子类覆盖后的版本。反之,如果利用组合,可以把装饰着混合着用,而且是在运行时。

而且还可以在任何时候实现新的装饰者增加新的行为,如果依赖继承,每当需要新行为时,还得修改现有的代码。

设计流程

按照上面给的UML类图,首先从Beverage下手,这代表了一个Component,所有的类型都直接或者间接的从它继承,来获取类型的一致性。而这是Decorator的关键。

 public abstract class Beverage
    {
        public virtual string Description { get; set; }
        public abstract double Cost();
    }

接着,定义一个ConcreteComponent:

 public sealed class Espresso:Beverage//代表某种咖啡
    {
        public Espresso()
        {
            Description = "Espresso";
        }
        public override double Cost()
        {
            return 1.2D;
        }
    }

由于在Beverage中Cost是抽象方法,所以要在子类中去override。

再定义一个装饰者的基类,也就是类图上的CondimentDecorator:

 public abstract class CondimentDecorator : Beverage
    {
        public override string Description { get; set; }
    }

然后就是定义具体的装饰类:

 public class Mocha:CondimentDecorator//摩卡,一种咖啡的“装饰”
    {
        private readonly Beverage _beverage;

        public Mocha(Beverage beverage)
        {
            this._beverage = beverage;
        }
        public override string Description
        {
            get => _beverage.Description + "," + " Mocha";
            set => base.Description = value; }
        public override double Cost()
        {
            return 1.5 + _beverage.Cost();
        }
    }

然后测试一下:

 class Program
    {
        static void Main(string[] args)
        {
            Beverage beverage=new Espresso();
            Console.WriteLine($"{beverage.Description},Cost is {beverage.Cost()}");
            beverage=new Mocha(beverage);//进一步装饰
            Console.WriteLine($"{beverage.Description},Cost is {beverage.Cost()}");
            beverage=new Mocha(beverage);//进一步装饰
            Console.WriteLine($"{beverage.Description},Cost is {beverage.Cost()}");
            Console.ReadKey();  
        }
    }

从上述代码可以看到这种装饰的行为正是依赖了统一的基类带来的类型的一致性,也证实了我们前面所言非虚。而在具体类的内部,根据对象的组合进一步扩展了对象的功能。由于对象组合上是针对抽象(抽象类)进行编程,所以很容易通过IOC容器来进行控制反转的实现。

 

推荐阅读