Head First设计模式——装饰者模式

2020-08-11 14:54:34 浏览数 (1)

前言:对于设计模式我们有时候在想是否有必要,因为实际开发中我们没有那么多闲工夫去套用这么多设计模式,也没有必要为了模式而模式。

通常这些模式会引入新的抽象层,增加代码的复杂度,但是当我们掌握了这些设计模式,

在系统中比较棘手或者需要以后修改扩展的地方采用了合适的设计模式会让我们的系统易于扩展维护甚至工作变得轻松很多。

对于这一点我深有体会,有时候设计的比较好的功能模块在后来客户改变需求的时候变得很容易且方便添加修改。

但是如果比较糟糕偷懒的方式会让我们对自己的代码修改变得害怕,害怕客户提需求,害怕去动自己的代码。

所以对于框架或者这些设计模式我们可以不用过度使用,但是要用的时候能有储备或者脑袋里面会闪现出对应的解决方案。

这样不光会提升我们的编码兴趣对我们开发的产品也更有信心。

本篇就聚焦Head First设计模式中的装饰者模式进行学习和总结。

咖啡店结算案例

咖啡店卖各种咖啡,根据调料不一样就会有不同价格的咖啡饮品可选,那么对应的咖啡饮品价格也不一样。

咖啡是需要按照基础价格 调料组合计算价格的,那按照继承的方式我们抽象一个饮料基类Beverage,Beverage拥有Cost抽象方法用于计算价格。

其他咖啡饮品继承Beverage实现Cost方法根据自己的调料计算价格。

代码语言:javascript复制
    public abstract class Beverage {
        public abstract void Description();
        public abstract float Cost();
    }

    public class DarkRoast : Beverage
    {
        public override void Description()
        {
            //深焙咖啡
        }
        public override float Cost()
        {
           //各种调料价格计算
        }
    }

对于继承的方式,其他子类都要去实现一遍价格计算无法复用,如果子类比较多那么继承的子类会“爆炸”,新增加子类对于我们来说就是一项重复工作。

如果说某一种调料的价格变化我们还得去每个子类里面改变价格。

那我们针对变化的调料部分是不是让他们按照实际需求组合,在子类中确定自己加哪些调料就行了,似乎这种方式会减少重写和维护难度。

按照这个思路将Beverage改造,将是否有调料定义成变量,然后Cost方法不再抽象而是提供实现。

代码语言:javascript复制
    public abstract class Beverage
    {
        //牛奶
        public bool Milk { get; set; }
        //糖
        public bool Suger { get; set; }
        //摩卡
        public bool Mocha { get; set; }
        public abstract void Description();
        public virtual float Cost()
        {
            float price = 0;
            if (Milk)
            {
                price  = 1;
            }
            if (Suger)
            {
                price  = 2;
            }
            if (Mocha)
            {
                price  = 3;
            }
            return price;
        }
    }

    public class DarkRoast : Beverage
    {
        public override void Description()
        {
            Console.WriteLine("深焙咖啡");
        }

        public override float Cost()
        {
            Milk = true;
            Suger = true;
            return 1.1f base.Cost();
        }
    }

这种方式比之前的继承的确好了许多,不过还是有如下几个问题:

1、调料价格改变会使我们改变现有代码。

2、需要添加新的调料,我们就需要添加新的方法并改变Beverage基类的Cost方法。

3、某些调料可能并不适合其他饮品,例如茶还要继承这些不属于它的调料。

4、如果顾客需要双份糖,Cost方法就会失效。

接下来我们就用"装饰者模式"来更好的设计该案例。

认识装饰者模式

这里有一个设计模式很重要的设计原则:类应该对扩展开放,对修改关闭(开闭原则)

装饰模式同样遵循开闭原则。

对于咖啡店来说,主体是饮料不变,变化的是调料。我们以饮料为主体,其他调料来“装饰”饮料。比如顾客想要加了Mocha(摩卡)的 DarkRoast(深焙咖啡),我们要做的是:

1、制造一个DarkRoast对象

2、以Mocha(摩卡)对象装饰它

3、调用Cost()方法,并依赖委托(非C# delegate,只是概念)将调料的价钱加上去

利用装饰者模式

首先我们修改主体饮料Beverage基类,GetDescription 返回描述,Cost由子类自己实现定价。

代码语言:javascript复制
    public abstract class Beverage
    {
        string description = "Unkonwn Beverage";
        public virtual string GetDescription() {
            return description;
        }
        public abstract float Cost();
    }

深焙咖啡类

代码语言:javascript复制
    public class DarkRoast : Beverage
    {
        public DarkRoast() {
            description = "深焙咖啡";
        }
        public override float Cost()
        {
            return 1.1f;
        }
    }

装饰类我们定义一个抽象基类Condiment(调料)并继承Beverage。

为什么要定义一个抽象装饰基类,因为装饰类可能需要抽象出其他方法,而且因为我们用装饰类去装饰了被装饰者后 我们本身也应该变成可已让别人装饰的类。所以装饰类继承CondimentDecorator,CondimentDecorator继承的是Beverage。此例暂未加入装饰类拥有的属性和方法

代码语言:javascript复制
    public abstract class CondimentDecorator:Beverage
    {
        
    }

我们再实现两个装饰类:Mik和Suger,用变量记录被装饰者,在构造函数里面设置被装饰者。

代码语言:javascript复制
    public class Milk : CondimentDecorator
    {
        //用变量记录被装饰者
        Beverage beverage;
        public Milk(Beverage beverage) {
            this.beverage = beverage;
        }

        public override string GetDescription()
        {
            return beverage.GetDescription()   " Milk";
        }
        public override float Cost()
        {
            return beverage.Cost()   1;
        }
    }

    public class Suger : CondimentDecorator
    {
        //用变量记录被装饰者
        Beverage beverage;
        public Suger(Beverage beverage)
        {
            this.beverage = beverage;
        }

        public override string GetDescription()
        {
            return beverage.GetDescription()   " Suger";
        }
        public override float Cost()
        {
            return beverage.Cost()   2;
        }
    }

编写测试结果:

总结

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

画出本例中装饰者模式类图以便理解和记忆

0 人点赞