zoukankan      html  css  js  c++  java
  • Head First设计模式之装饰者模式(Decorator Pattern)

    前言:

         本节将深度讨论继承滥用问题,将会学到使用对象组合的方式,在运行时装饰类,在不修改任何底层代码的情况下,给对象赋予新的职责。

    1.    基本需求:咖啡连锁店业务扩张需要重新设计订单系统

    背景:由于StarBuzz咖啡连锁店业务扩张,准备更新订单系统,以合乎他们的饮料供应要求。

    他们原来的类设计如下:

     

    用户在购买咖啡的时候,可以能会要求在咖啡中加入各种调料,StarBuzz会根据用户加入的不同调收取不同费用,新的订单系统必须考虑到这些调料部分。

    1.1 第一次设计

     

    以上的每一个类的Cost()方法将会算出咖啡加上订单的各种调料的价钱。虽然可以满足需求,但是这样会需要很多很多的类,而且也违反了OO的设计原则。

    1.2 第二次设计

    不需要创建那么多的类,只需要通过利用实例变量和继承,就可以追踪调料。

    设计如下:

     

    这样做,确实可以暂时满足需求,但是还会存在一些潜在的隐患,如下:

    l  调料价格变动会改变原有代码

    l  新增调料,除了加上新增方法外,还需要改变超类中的Cost()方法

    l  依赖继承,子类将继承一些对自身并不合适的方法

    l  部分需求无法满足:如双倍摩卡咖啡

    l  违反了开放—关闭原则

    2.    引入装饰者模式

    2.1 开发----关闭原则

    在上一节的第二次设计中我们可以看出这种设计方法明显的违背了“开发—关闭”原则,那什么是开闭原则呢?定义如下:

    开发—关闭原则:类应该是对扩展开放,对修改关闭。

    我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为,这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。遵循开开放—关闭原则,通常会引入新的抽象层次,增加代码的复杂度,需要选择在设计中最可能改变的地方,然后应用开发-关闭原则。

    2.2 认识装饰者模式,并以装饰者构造饮料订单

    在1中我们已经了解到通过继承无法完全解决问题,这里我们以饮料为主体,然后在运行时以调料来“装饰”饮料。例如:如果客户想要摩卡和奶泡深焙咖啡,如下:

    l  拿一个深焙(DarkRoast)对象

    l  以摩卡(Mocha)对象装饰它

    l  以奶泡(Whip)对象装饰它

    l  调用Cost()方法,并依赖委托(delegate)将调料的价钱加上去

    说明:以DarkRoast对象开始,顾客想要摩卡(Mocha),所以建立一个Mocha对象,用它将DarkRoast对象包起来,顾客想要奶炮(Whip),所以需要建立一个Whip装饰者,并用它将Mocha对象包起来。最后算钱,通过调用最外层的装饰者Whip的Cost()方法就可以办到。其调用过程如下:

    Whip.Cost()àMocha.Cost()àDarkRoast.Cost()(返回DarkRoast的价钱)àMocha.Cost()(Mocha在DarkRoast的结果上加上自己的价钱)àWhip.Cost()(Whip在Mocha的返回结果上加上自己的价钱)

    基于以上的这种分析,我们可以得出以下的结论:

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

    可以用一个或者多个装饰者包装一个对象。

    因为装饰者和被装饰者有相同的超类型,所以在任何需要原始对象的场合,可以用装饰过的对象代替它。

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

    对象可以在任何时候被装饰,所以可以在运行时动态的用你喜欢的装饰者来装饰对象。

    2.3 定义装饰者模式

    装饰者模式:

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

    设计类图如下:

     

    依据这种装饰者模式的类图,我们设计出StarBuzz的类图,也让它符合这种结构设计,类图如下:

     

    装饰者和被装饰者都继承自Beverage类,也就是拥有共同的超类,这里,我们是利用继承达到“类型匹配”,而不是利用继承获得“行为”。

    装饰者和组件组合时,就是在加入新的行为,所得的新行为并不是继承自超类,而是由组合对象得来的。

    如果依赖继承,那么类的行为只能在编译时静态决定,行为不是来自超类就是子类覆盖后的版本,每当需要新行为时,还得修改现有代码。如果利用组合,就可以动态的实现新的装饰者增加新的行为。

    3 用装饰者模式实现咖啡店需求

    根据在2.3中设计的咖啡店的类图,下面就进行具体的编码实现:

    3.1 Beverage类(抽象组件)

    /// Description: Beverage抽象类
        /// </summary>
        public abstract class Beverage
        {
            public string description = "Unknown Beverage";
            public abstract string GetDescription();
            public abstract double Cost();
        }

    3.2Condiment(调料)基类(继承自Beverage基类,抽象装饰者)

    /// Description:调料基类、派生类
        /// </summary>
        public abstract class CondimentDecorator:Beverage
        {
            //public  abstract string GetDescription();
        }

    3.3饮料类(继承Beverage基类,具体组件)

     public class Espresso:Beverage
        {
            public Espresso()
            {
                description = "Espresso";//设置饮料的表述,description继承自Beverage类的实例变量
            }
    
            public override double Cost()
            {
                return 1.99;
            }
    
            public override string GetDescription()
            {
                return description;
            }
        }
    
        public class HouseBlend : Beverage
        {
            public HouseBlend()
            {
                description = "HouseBlend";
            }
    
            public override double Cost()
            {
                return 0.89;
            }
    
            public override string GetDescription()
            {
                return description;
            }
        } 
    
        public class DarkRoase : Beverage
        {
            public DarkRoase()
            {
                description = "DarkRoase";
            }
    
            public override double Cost()
            {
                return 1.11;
            }
            public override string GetDescription()
            {
                return description;
            }
        }
    
        public class Decat : Beverage
        {
            public Decat()
            {
                description = "Decat";
            }
    
            public override double Cost()
            {
                return 1.22;
            }
    
            public override string GetDescription()
            {
                return description;
            }
        }

    3.4调料类(装饰者)

    public class Mocha : CondimentDecorator
        {
            Beverage beverage;
            public Mocha(Beverage beverage)
            {
                this.beverage = beverage;
            }
    
            public override string GetDescription()
            {
                return beverage.GetDescription() + ",Mocha";
            }
            public override double Cost()
            {
                return 0.2 + beverage.Cost();
            }
        }
    
        public class Soy : CondimentDecorator
        {
            Beverage beverage;
            public Soy(Beverage beverage)
            {
               this.beverage = beverage;
            }
    
            public override string GetDescription()
            {
                return beverage.GetDescription() + ",Soy";
            }
    
            public override double Cost()
            {
                return 0.3 + beverage.Cost();
            }
        }
    
        public class Whip : CondimentDecorator
        {
            Beverage beverage;
            public Whip(Beverage beverage)
            {
                this.beverage = beverage;
            }
    
            public override string GetDescription()
            {
                return beverage.GetDescription() + ",Whip";
            }
    
            public override double Cost()
            {
                return 0.3 + beverage.Cost();
            }
        }

    3.5 测试

    Beverage.Beverage beverage = new Beverage.Espresso();
                Console.WriteLine(beverage.GetDescription() + " $ " + beverage.Cost());
                Beverage.Beverage beverage1 = new Beverage.DarkRoase();
                beverage1 = new Beverage.Mocha(beverage1);
                beverage1 = new Beverage.Mocha(beverage1);
                beverage1 = new Beverage.Whip(beverage1);
                Console.WriteLine(beverage1.GetDescription() + " $ " + beverage1.Cost());
    
                Beverage.Beverage beverage2 = new Beverage.HouseBlend();
                beverage2 = new Beverage.Soy(beverage2);
                beverage2 = new Beverage.Mocha(beverage2);
                beverage2 = new Beverage.Whip(beverage2);
                Console.WriteLine(beverage2.GetDescription() + " $ " + beverage2.Cost());

    结果如下图:

     

    4        总结

    通过本章的学习,我们可以学到以下知识:

    l  OO原则:

    封装变化

    多用组合,少用继承

    针对接口编程,不针对实现编程

    为交互对象之间的松耦合设计而努力

    对扩展开放,对修改关闭(本章节新学习的OO原则)

    l  OO模式

    装饰者模式—动态地将责任附加到对象上,想要扩展功能,装饰者提供有别于继承的另一种选择。

    l  要点归纳

    继承和装饰者都可以让我们扩展行为,但继承不是弹性设计的最佳方案。

    装饰者模式意味着一群装饰者类,装饰者类反应了被装饰组件的类型,可以用多个装饰者包装对象。

    装饰者可以在被装饰者的行为之前或者之后加上自己的行为,甚至将被装饰者的行为取代,以到达特定目的。

    装饰者模式会导致设计中出现许多小对象,过度使用会使程序变得复杂。

  • 相关阅读:
    weexpack build android 和 weexpack run android 报错 及 解决方案
    weexapp 开发流程(三)其他页面创建
    svn 创建分支、切换分支 及 合并分支 操作
    github 新建远程仓库 及 删除远程仓库
    photoshop 前端常用技巧
    vue2.0 常用的 UI 库
    weex 小结
    Android-studio 连接真机 调试weex项目
    js中Math之random,round,ceil,floor的用法总结
    基于canvas图像处理的图片 灰色图像
  • 原文地址:https://www.cnblogs.com/Olive116/p/5276588.html
Copyright © 2011-2022 走看看