• 装饰者模式


    一、基本概述

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

    二、分析说明

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

    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》进行整理

  • 相关阅读:
    100 余个网页设计优化案例(用户体验、交互优化等方面)
    Tinyhttpd 源代码初步解读
    emlog pro 文章编辑器(editor.md)的快捷键
    什么是 CSS 设计模式
    原生 JS 实现 HTML 转 Markdown,以及其实现逻辑(html2md.js 或 html2markdown.js)
    【Example】C++ 回调函数及 std::function 与 std::bind
    【Example】C++运算符重载
    【小记】Linux find 配合 rm 命令安全批量删除文件
    【小记】Linux 快速查找并结束僵尸进程
    【Example】C++ 标准库多线程同步及数据共享 (std::future 与 std::promise)
  • 原文地址:https://www.cnblogs.com/zwt-blog/p/6488891.html
Copyright © 2020-2023  润新知