C# Design Patterns (3) - Decorator

Decorator Pattern (装饰模式)

装饰模式可「动态」地给一个对象添加一些额外的职责,提供有别于「继承」的另一种选择。就扩展功能而言,Decorator Pattern 透过 Aggregation (聚合) 的特殊应用,降低了类与类之间的耦合度,会比单独使用「继承」生成子类更为灵活。

一般用「继承」来设计子类的做法,会让程序变得较僵硬,其对象的行为,是在「编译」时期就已经「静态」决定的,而且所有的子类,都会继承到相同的行为;然而,若用「装饰模式」以及 UML 的 Aggregation 的设计,来扩展对象的行为,就能弹性地 (flexible) 将多个「装饰者」混合着搭配使用,而且是在「执行」时期「动态」地进行扩展。

此外,若用一般「继承」的做法,每当对象需要新行为时,必须修改既有的代码、重新编译;但若透过「装饰模式」,则无须修改既有代码。

The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
                                 - Design Patterns: Elements of Reusable Object-Oriented Software

图 1 此图为 Decorator 模式的经典 Class Diagram

01_Shell / Program.cs

using System;

namespace _01_Shell

{

    //客户端程序

    class Program

    {

        static void Main(string[] args)

        {

            ConcreteComponent c = new ConcreteComponent();

            ConcreteDecoratorA d1 = new ConcreteDecoratorA();

            ConcreteDecoratorB d2 = new ConcreteDecoratorB();

//让 ConcreteDecorator 在运行时,动态地给 ConcreteComponent 增加职责

            d1.SetComponent(c);

            d2.SetComponent(d1);

            d2.Operation();

Console.Read();

        }

    }

//装饰者、被装饰者的共同基类

    abstract class Component

    {

        public abstract void Operation();

    }

//被装饰者。具体被装饰对象。

    //此为在「执行」时期,会被 ConcreteDecorator 具体装饰者,「动态」添加新行为(职责)的对象

    class ConcreteComponent : Component

    {

        public override void Operation()

        {

            Console.WriteLine("具体被装饰对象的操作");

        }

    }

//装饰者。

    //此为所有装饰者,应共同实现的抽象类或接口

    abstract class Decorator : Component

    {

        //以父类声明的字段。

        //实现 UML Class Diagram 的 Aggregation,指向 Component 对象的指针。

        protected Component component;

public void SetComponent(Component component)

        {

            this.component = component;

        }

public override void Operation()

        {

            if (component != null)

            {

                component.Operation();

            }

        }

    }

//具体装饰者 A

    //此为在「执行」时期,替 ConcreteComponent「动态」添加新行为(职责)的对象

    class ConcreteDecoratorA : Decorator

    {

        //装饰者可以添加新的栏位

        private string addedState;

public override void Operation()

        {

            base.Operation();

            addedState = "New State";

            Console.WriteLine("具体装饰对象 A 的操作");

        }

    }

//具体装饰者 B

    //此为在「执行」时期,替 ConcreteComponent「动态」添加新行为(职责)的对象

    class ConcreteDecoratorB : Decorator

    {

        public override void Operation()

        {

            base.Operation();

            AddedBehavior();

            Console.WriteLine("具体装饰对象 B 的操作");

        }

//装饰者可以添加新的方法

        private void AddedBehavior()

        {

        }

    }

} // end of namespace

/*

执行结果:

具体被装饰对象的操作

具体装饰对象 A 的操作

具体装饰对象 B 的操作

*/

上方图 1 的 Class Diagram,以及「Shell (壳)」示例中,ConcreteComponent 即为此一模式中的「被装饰者」,而 Decorator 抽象类及其具体子类 ConcreteDecoratorA、ConcreteDecoratorB 即为「装饰者」。此外,这个模式中最核心的就是 Decorator 这个抽象类,它用一个以父类 Component 声明的变量 component,实现 UML 中的 Aggregation (聚合,有的博文也统称为「组合」或「合成」),亦即为指向 Component 对象的指针,达到我们前述 - 「动态」进行扩展的目的。

至于其设计的原理,以下我引用博客园一位前辈 Justin 两年前所写博文的一段内容 [1],这段是我认为对 Decorator 模式极优的说明。读者亦可搭配参考上方代码里我添加的注释,稍后我会再补充说明。

--------------------------------------------------------

Decorator 是装饰者模式里非常特殊的一个类,它既继承于 Component【IS A关系】,又维护一个指向 Component 实例的引用【HAS A关系】,换个角度来说,Decorator 跟 Component 之间,既有动态组合关系,又有静态继承关系,WHY? 这里为什么要这么来设计?上面我们说过,组合的好处是可以在运行时给对象增加职责,Decorator【HAS A】Component 的目的,是让 ConcreteDecorator 可以在运行时动态地给 ConcreteComponent 增加职责,这一点相对来说还比较好理解;那么 Decorator 继承于 Component 的目的是什么?在这里继承的目的只有一个,那就是可以统一「装饰者」和「被装饰者」的接口,换个角度来说,不管是 ConcretComponent 还是 ConcreteDecorator,它们都是最顶层 Component 基类,用户代码可以把它们统一看作 Component 来处理,这样带来的更深一层好处就是,「装饰者」对象,对「被装饰者」对象的功能职责扩展,对用户代码来说是完全透明的,因为用户代码引用的都是 Component,所以就不会因为「被装饰者」对象在被装饰后,引用它的用户代码发生错误,实际上不会有任何影响,因为装饰前后,用户代码引用的都是 Component 类型的对象,这真是太完美了!「装饰模式」通过继承,实现统一了「装饰者」和「被装饰者」的接口,通过组合获得了在运行时动态扩展「被装饰者」对象的能力。

我们再举个生活中的例子,俗话说“人在衣着马在鞍”,把这用「装饰模式」的语境翻译一下,“人通过漂亮的衣服装饰后,男人变帅了,女人变漂亮了;”。对应上面的类图,这里「人」对应于 ConcreteComponent,而「漂亮衣服」则对应于 ConcreteDecorator;换个角度来说,人和漂亮衣服组合在一起【HAS A】,有了帅哥或美女,但是他们还是人【IS A】,还要做人该做的事情,但是可能会对异性更有吸引力了(扩展功能)!

--------------------------------------------------------

上方 Justin 前辈,其文章的「煮咖啡」示例 [1],是引用自 O'Reilly 出版社的「Head First 设计模式」这本书的第三章 [10]。该文的煮咖啡类图中,HouseBlend (家常咖啡)、DarkRoast (深度烘培咖啡)、Espresso (意大利特浓咖啡)、Decaf (无咖啡因咖啡),这四种咖啡 (饮料),即为「被装饰者」,等同本帖上图 1 中的 ConcreteComponent 类;该文类图中的 CondimentDecorator 抽象类,等同本帖上图 1 中最重要的 Decorator 抽象类,亦即「装饰者」的抽象定义;该文类图中的 Milk、Mocha、Soy、Whip 这四种调料 (调味品),即为具体的「装饰者」,亦即在本帖一开始提到,这四种调料,可弹性地 (flexible) 混合着搭配使用,而且是在「执行」时期「动态」地进行扩展,亦即动态地装饰 HouseBlend、DarkRoas、Espresso、Decaf 这四种咖啡。

接下来,我们用另一个简单的例子来实现 Decorator 模式,并改用 ASP.NET 网页程序来实现。类图及代码如下方图 2 所示,执行结果如下图 3 所示。

此为一个西餐牛排馆的计费程序,这间牛排馆有两种主菜 - 牛排和猪排,此为「被装饰者」;有四种副菜 - 面条、生菜沙拉、饮料 (热饮或冷饮)、甜点,此为「装饰者」。客人点餐时,可点一种主菜,副菜可点一份、可点多份,也可不点 (有弹性地将多个「装饰者」混合着搭配);每样主菜和副菜都有各自的价格,全部相加后,即为一或多位客人的用餐费用。而且我们希望,将来这个计费程序写好后,未来即使要修改价格,或添加新的主菜和副菜时,都不用再修改既有的程序。

图 2 示例 02_Steak.aspx.cs 的 Class Diagram

02_Steak.aspx.cs

using System;

using com.cnblogs.WizardWu.sample02;       //客端程序调用服务器端的组件和类

//客端程序

public partial class _02_Steak : System.Web.UI.
Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        //点一客猪排,不需要副菜。并列出它的描述与价格

        Meal meal1 = new PorkChop();

        //Meal meal1 = new BeefSteak();

        Response.Write(meal1.Description + " = $ " + meal1.cost() + "

");

//点一客猪排,副菜只要面条。并列出它的描述与价格

        Meal meal2 = new PorkChop();

        meal2 = new Noodle(meal2);          //用 Noodle 装饰它 (运行时动态地增加职责)

        Response.Write(meal2.Description + " = $ " + meal2.cost() + "

");

//点一客猪排,因为这个人食量大,副菜要两份面条、一杯饮料(热饮)、一盘甜点。并列出它的描述与价格

        Meal meal3 = new PorkChop();

        meal3 = new Noodle(meal3);          //用 Noodle 装饰它

        meal3 = new Noodle(meal3);          //用第二个 Noodle 装饰它

        meal3 = new Drink(meal3, true);    //用 Drink 装饰它

        meal3 = new Dessert(meal3);         //用 Dessert 装饰它

        Response.Write(meal3.Description + " = $ " + meal3.cost() + "

");

//第四个人不吃猪肉,因此主菜改点一客牛排,副菜要一盘沙拉、一杯饮料(冷饮)。并列出它的描述与价格

        Meal meal4 = new BeefSteak();

        meal4 = new Salad(meal4);            //用 Salad 装饰它 (运行时动态地增加职责)

        meal4 = new Drink(meal4, false);   //用 Drink 装饰它

        Response.Write(meal4.Description + " = $ " + meal4.cost() + "

");

    }

}

//服器端程序

namespace com.cnblogs.WizardWu.sample02

{

    //装饰者(副菜)、被装饰者(主菜)的共同基类。类似前一例的 Component 抽象类

    abstract class Meal

    {

        protected string description = "西餐";

public virtual string Description

        {

            get { return description; }

            set { description = value; }

        }

abstract public int cost();        //必须在子类实作的抽象方法,除非该个子类也是抽象类

    }

//主菜(被装饰者)。类似前一例的 ConcreteComponent 类

    class PorkChop : Meal                //主菜 - 猪排。以后执行时,才会动态被副菜装饰

    {

        public PorkChop()                  //构造函数

        {

            Description = "猪排"

时间: 2024-08-15 04:12:13

C# Design Patterns (3) - Decorator的相关文章

艾伟:C# Design Patterns (3) - Decorator

Decorator Pattern (装饰模式) 装饰模式可「动态」地给一个对象添加一些额外的职责,提供有别于「继承」的另一种选择.就扩展功能而言,Decorator Pattern 透过 Aggregation (聚合) 的特殊应用,降低了类与类之间的耦合度,会比单独使用「继承」生成子类更为灵活. 一般用「继承」来设计子类的做法,会让程序变得较僵硬,其对象的行为,是在「编译」时期就已经「静态」决定的,而且所有的子类,都会继承到相同的行为:然而,若用「装饰模式」以及 UML 的 Aggregat

艾伟_转载:C# Design Patterns (3) - Decorator

Decorator Pattern (装饰模式) 装饰模式可「动态」地给一个对象添加一些额外的职责,提供有别于「继承」的另一种选择.就扩展功能而言,Decorator Pattern 透过 Aggregation (聚合) 的特殊应用,降低了类与类之间的耦合度,会比单独使用「继承」生成子类更为灵活. 一般用「继承」来设计子类的做法,会让程序变得较僵硬,其对象的行为,是在「编译」时期就已经「静态」决定的,而且所有的子类,都会继承到相同的行为:然而,若用「装饰模式」以及 UML 的 Aggregat

Head First Design Patterns

    在更大的计划之前,先温习一下Design Pattern的功课.    看了<Head First Design Patterns>里讲Decorator的样章,发现JOLT大奖不是白拿的,叙事能力之强,表达之清晰,不是那些满腹经伦的老先生可以比的.而且整个Pattern的讲述过程循序渐进,真的可以保证--小白都能学会设计模式.    可惜就只有样章.Head First系列的电子书都不好找,只好还是翻出老先生们的书来看.    这次温习很快做完,其实GOF80%的模式,都是基于一个原

Design Patterns: Solidify Your C# Application Arch

Design Patterns: Solidify Your C# Application Architecture with Design Patterns中文版(下篇)    optimizer(翻译)   关键字     设计模式 singleton strategy decorator composite state   出处     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnmag01/html

《Design Patterns》学习总结

在时间比较空余的时候,又找到一本一直想看的书,就是这本名为<Design Patterns>(Gang of Four)的著作.本书通过类似Window Doc的程序,来揭开设计模式学习序幕,通过分析设计程序时遇到的困难,引出可以解决问题的设计模式,从而引导你更全面的掌握设计模式.案例程序的设计引出8个设计模式,案例程序设计完成后就是单独的23个经典设计模式,可以单个查阅,单个阅读. Abstract Factory模式 这个模式似乎离我比较远,因为本人还从未开发过在此模式讲解过程中介绍到的设

艾伟_转载:C# Design Patterns (4) - Proxy

本帖介绍 Proxy Pattern (代理模式). Proxy Pattern (代理模式) The Proxy Pattern provides a surrogate or placeholder for another object to control access to it...                                  - Design Patterns: Elements of Reusable Object-Oriented Software 在 Go

Mobile UI Design Patterns: 10+ Sites for Inspiration

原文:http://sixrevisions.com/user-interface/mobile-ui-design-patterns-inspiration/ User interface design patterns are solutions to common design challenges, such as navigating around an app, listing data or providing feedback to users. Mobile apps and

Comparing Two High-Performance I/O Design Patterns

Summary This article investigates and compares different design patterns of high performance TCP-based servers. In addition to existing approaches, it proposes a scalable single-codebase, multi-platform solution (with code examples) and describes its

C# Design Patterns (1) - Factory Method

Simple Factory Pattern (简单工厂模式) 特性: 把类的实例化工作,集中到一个「工厂类」去处理,亦即将 new instance 的工作,都交给一个「工厂」去处理,而不要分散写在各个类中.客户端程序,与创建实例 (对象) 的工作必须隔离,亦即「解耦」,客户端程序只要专注于自己的业务逻辑.适用于客户端程序在开发过程中,尚无法预知要创建的具体类型.产品具体的实现能和客户端隔离,便于事后抽换. Simple Factory Pattern (简单工厂模式).Factory Met