c#

位置:IT落伍者 >> c# >> 浏览文章

C# Design Patterns (3) - Decorator


发布日期:2020年03月28日
 
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 ObjectOriented Software

此图为 Decorator 模式的经典 Class Diagram

_Shell / Programcs

using System;

namespace __Shell

{

//客户端程序

class Program

{

static void Main(string[] args)

{

ConcreteComponent c = new ConcreteComponent();

ConcreteDecoratorA d = new ConcreteDecoratorA();

ConcreteDecoratorB d = new ConcreteDecoratorB();

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

dSetComponent(c);

dSetComponent(d);

dOperation();

ConsoleRead();

}

}

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

abstract class Component

{

public abstract void Operation();

}

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

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

class ConcreteComponent : Component

{

public override void Operation()

{

ConsoleWriteLine(具体被装饰对象的操作);

}

}

//装饰者

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

abstract class Decorator : Component

{

//以父类声明的字段

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

protected Component component;

public void SetComponent(Component component)

{

ponent = component;

}

public override void Operation()

{

if (component != null)

{

componentOperation();

}

}

}

//具体装饰者 A

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

class ConcreteDecoratorA : Decorator

{

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

private string addedState;

public override void Operation()

{

baseOperation();

addedState = New State;

ConsoleWriteLine(具体装饰对象 A 的操作);

}

}

//具体装饰者 B

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

class ConcreteDecoratorB : Decorator

{

public override void Operation()

{

baseOperation();

AddedBehavior();

ConsoleWriteLine(具体装饰对象 B 的操作);

}

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

private void AddedBehavior()

{

}

}

} // end of namespace

/*

执行结果

具体被装饰对象的操作

具体装饰对象 A 的操作

具体装饰对象 B 的操作

*/

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

至于其设计的原理以下我引用博客园一位前辈 Justin 两年前所写博文的一段内容 []这段是我认为对 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 前辈其文章的「煮咖啡」示例 []是引用自 OReilly 出版社的「Head First 设计模式」这本书的第三章 []该文的煮咖啡类图中HouseBlend (家常咖啡)DarkRoast (深度烘培咖啡)Espresso (意大利特浓咖啡)Decaf (无咖啡因咖啡)这四种咖啡 (饮料)即为「被装饰者」等同本帖上图 中的 ConcreteComponent 类该文类图中的 CondimentDecorator 抽象类等同本帖上图 中最重要的 Decorator 抽象类亦即「装饰者」的抽象定义该文类图中的 MilkMochaSoyWhip 这四种调料 (调味品)即为具体的「装饰者」亦即在本帖一开始提到这四种调料可弹性地 (flexible) 混合着搭配使用而且是在「执行」时期「动态」地进行扩展亦即动态地装饰 HouseBlendDarkRoasEspressoDecaf 这四种咖啡

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

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

示例 _Steakaspxcs 的 Class Diagram

_Steakaspxcs

using System;

using blogsWizardWusample; //客户端程序调用服务器端的组件和类

//客户端程序

public partial class __Steak : SystemWebUIPage

{

protected void Page_Load(object sender EventArgs e)

{

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

Meal meal = new PorkChop();

//Meal meal = new BeefSteak();

ResponseWrite(mealDescription + = $ + st() +

);

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

Meal meal = new PorkChop();

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

ResponseWrite(mealDescription + = $ + st() +

);

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

Meal meal = new PorkChop();

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

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

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

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

ResponseWrite(mealDescription + = $ + st() +

);

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

Meal meal = new BeefSteak();

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

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

ResponseWrite(mealDescription + = $ + st() +

);

}

}

//服务器端程序

namespace blogsWizardWusample

{

//装饰者(副菜)被装饰者(主菜)的共同基类类似前一例的 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 = 猪排;

}

public override int cost() //具体对象的操作

{

return ; //猪排的价格现在不需要管「副菜」的价格直接返回猪排的价格即可

}

}

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

class BeefSteak : Meal //主菜 牛排以后执行时才会动态被副菜装饰

{

public BeefSteak() //构造函数

{

Description = 牛排;

}

public override int cost() //具体对象的操作

{

return ; //牛排的价格现在不需要管「副菜」的价格直接返回牛排的价格即可

}

}

//装饰者类似前一例的 Decorator 抽象类此为所有「副菜(装饰者)」的父类

//功能一未来所有可能添加的副菜其共同的属性行为都可放在这个类里

//功能二用来「分类」哪些是主菜哪些是副菜

abstract class CondimentDecorator : Meal //副菜用来「动态」装饰主菜和 Meal 基类亦即在运行时动态地替他们增加职责

{

//以父类声明的字段

//实现 UML Class Diagram 的 Aggregation指向 Meal 对象的指针

protected Meal meal;

public override string Description

{

get { return mealDescription + + + description; }

set { description = value; }

}

}

//副菜(装饰者)具体装饰类 面条用来动态装饰主菜 牛排或猪排

class Noodle : CondimentDecorator //类似前一例的 ConcreteDecoratorA

{

public Noodle(Meal meal) //构造函数

{

Description = 面条;

al = meal;

}

public override int cost() //具体装饰对象 A 的操作

{

return + st();

}

}

//副菜(装饰者)具体装饰类 沙拉用来动态装饰主菜 牛排或猪排

class Salad : CondimentDecorator //类似前一例的 ConcreteDecoratorB

{

public Salad(Meal meal) //构造函数

{

Description = 生菜沙拉;

al = meal;

}

public override int cost() //具体装饰对象 B 的操作

{

return + st();

}

}

//副菜(装饰者)具体装饰类 饮料用来动态装饰主菜 牛排或猪排

class Drink : CondimentDecorator //类似前一例的 ConcreteDecoratorC

{

private bool isHot;

public bool IsHot

{

get { return isHot; }

}

public Drink(Meal meal bool isHot) //构造函数

{

Description = (isHot) ? 热饮 : 冷饮;

al = meal;

thisisHot = isHot;

}

public override int cost() //具体装饰对象 C 的操作冷饮比热饮多收 块钱

{

return (isHot) ? ( + st()) : ( + st());

}

}

//副菜(装饰者)具体装饰类 甜点用来动态装饰主菜 牛排或猪排

class Dessert : CondimentDecorator //类似前一例的 ConcreteDecoratorD

{

public Dessert(Meal meal) //构造函数

{

Description = 甜点;

al = meal;

}

public override int cost() //具体装饰对象 D 的操作

{

return + st();

}

}

} // end of namespace

示例 _Steakaspxcs 的执行结果扩展功能时主菜和副菜可任意混合搭配使用

原理与本帖之前提过的相同将来店里若要推出 羊排鱼排…等新的主菜 (被装饰者) 时只要增加一个类去继承 Meal 这个抽象的基类即可若要推出新的副菜 (装饰者) 也一样只要增加一个类去继承 CondimentDecorator 抽象类即可我们透过这个重要的 CondimentDecorator 抽象类实现 UML 的 Aggregation (聚合) 观念以「装饰模式」取代传统「继承」的做法也同时降低了「主菜」「副菜」这两种类之间的耦合度

这个 CondimentDecorator 抽象类的作用还可将未来所有种类的副菜一些「共同的」行为或职责写在里面而且它还可用来区分哪些类是主菜哪些类是副菜因为主菜副菜都是继承自 Meal 这个最顶层的基类若没有这个 CondimentDecorator 抽象类将来 Class Diagram 变得很复杂时会不易分辨某个类是主菜或副菜

此外也因为主菜 (被装饰者)副菜 (装饰者) 都是继承自 Meal 这个最顶层的基类所以客户端程序 (Page_Load) 在引用他们的对象时把 new 出来的主菜实例new 出来的副菜实例再抛入其他副菜实例的构造函数中也会相容不会发生错误在相互引用和「运行」时期的「动态」扩展上变得很有弹性

最后补充一点Decorator 模式中装饰者类对象在组合时的「顺序」亦很重要本帖图 ConcreteDecoratorAConcreteDecoratorBConcreteDecoratorC 等具体装饰者在动态添加和组合时在某些实际应用中必须依照一定的顺序若顺序相反可能会导致程序执行结果不合理但本帖所提供的几个例子中并不需要考虑到这点

Decorator Pattern 适用的情景

你拥有一个已存在的组件类却无法继承它 (subclassing)

能够动态地为对象添加职责 (添加状态和行为)

改变类中的成员和行为但不影响其他对象

希望能便于职责的撤消

不想用「继承」来扩展行为其中一种原因是避免当一些功能要交叉搭配引用时单独用「继承」来设计会产生太多的子类太复杂的类图结构 [] []另一种考量可能是因为类的定义被隐藏或类的定义不能用于生成子类

Decorator Pattern 的优点

可避免单独使用「继承」时在扩展时不够弹性且可能衍生过多的子类

扩展时不需要修改既有的代码

可在执行时期动态地添加新行为 (职责)

Decorator Pattern 的缺点

可能会在程序中出现许多的小型类亦即需要编写很多 ConcreteDecorator 类 (具体装饰者)

若过度使用 Decorator 模式会让程序逻辑变得很复杂

别人较不易理解设计方式及代码排查故障追蹤和调试也比较困难

Decorator Pattern 的其他特性

每个要装饰的功能都放在单独的类中

我们可以用无数个装饰者去包装一个组件

「装饰者」可以在「被装饰者」前面或后面添加自己的行为甚至将「被装饰者」的行为整个取代掉以达到特定的目的

「被装饰者」并不需要知道它已经被「装饰」过了亦即 Component 类 (对象) 并不需要知道 Decorator 类 (对象) 的存在且 Decorator 也仅仅认识 Component

最后再补充在 Java I/ONET I/ONET SystemWindowsControls 命名空间中很多地方都有用到 Decorator 模式来设计 [] [] []例如我们查询 MSDN Library会发现 NET x 版本的 Windows Forms 其 API 里甚至还有增加一个新的 Decorator 类如下

SystemObject

SystemWindowsThreadingDispatcherObject

SystemWindowsDependencyObject

SystemWindowsMediaVisual

SystemWindowsUIElement

SystemWindowsFrameworkElement

SystemWindowsControlsDecorator

SystemWindowsControlsViewbox

SystemWindowsControlsDecorator 类

提供在单个子元素(如 Border 或 Viewbox)上或周围应用效果的元素的基类

cn/library/ntrolsdecoratoraspx

cn/library/ntrolsdecorator(VS)aspx

以下列出 Decorator Pattern用于实际程序的四种方式 []

装饰模式非常适合于「图形程序」亦适合于「视频和声音」比如视频流可用不同的比率进行压缩而声音可以输入给同声传译服务

装饰模式如此有用以致于 NET 已经有实际的 Decorator 类了如刚才提到的在 SystemWindowsControls 命名空间中提供了一些基类可用于给其他的图形元素添加特殊效果Border 类和 Viewbox 类

在如今的移动设备上Web 浏览器及其他应用中也大量运用 Decorator Pattern比如这些设备上可以创建适合小屏幕显示的对象跟标准桌面机上的浏览器相比这些屏幕可能也含有滚动条但不含有标题栏 []

从更一般的层次上NET 的 I/O API 中到处都是装饰模式 []如下方的体系

SystemObject

SystemMarshalByRefObject

SystemIOStream

SystemIOBufferedStream

SystemIOFileStream

SystemIOMemoryStream

SystemNetSocketsNetworkStream

SystemSecurityCryptographyCryptoStream

上方体系的最后五个子类有的就装饰了 Steram 这个父类 (如BufferedStream 类)因为它们从它继承同时又含有 Stream 类的实例而且这个实例是子类被构造时传递进去的 (如同本帖第二个西餐店的示例中副菜的构造函数的写法)如下方 MSDN Library 代码的 BufferedStream 类其构造函数的第一个参数即为其父类 SystemIOStream 的变量 stream

BufferedStream 构造函数

public BufferedStream(

Stream stream

)

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

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

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

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

示例 _Steakaspxcs 的 Class Diagram

_Steakaspxcs

using System;

using blogsWizardWusample; //客户端程序调用服务器端的组件和类

//客户端程序

public partial class __Steak : SystemWebUIPage

{

protected void Page_Load(object sender EventArgs e)

{

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

Meal meal = new PorkChop();

//Meal meal = new BeefSteak();

ResponseWrite(mealDescription + = $ + st() +

);

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

Meal meal = new PorkChop();

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

ResponseWrite(mealDescription + = $ + st() +

);

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

Meal meal = new PorkChop();

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

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

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

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

ResponseWrite(mealDescription + = $ + st() +

);

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

Meal meal = new BeefSteak();

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

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

ResponseWrite(mealDescription + = $ + st() +

);

}

}

//服务器端程序

namespace blogsWizardWusample

{

//装饰者(副菜)被装饰者(主菜)的共同基类类似前一例的 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 = 猪排;

}

public override int cost() //具体对象的操作

{

return ; //猪排的价格现在不需要管「副菜」的价格直接返回猪排的价格即可

}

}

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

class BeefSteak : Meal //主菜 牛排以后执行时才会动态被副菜装饰

{

public BeefSteak() //构造函数

{

Description = 牛排;

}

public override int cost() //具体对象的操作

{

return ; //牛排的价格现在不需要管「副菜」的价格直接返回牛排的价格即可

}

}

//装饰者类似前一例的 Decorator 抽象类此为所有「副菜(装饰者)」的父类

//功能一未来所有可能添加的副菜其共同的属性行为都可放在这个类里

//功能二用来「分类」哪些是主菜哪些是副菜

abstract class CondimentDecorator : Meal //副菜用来「动态」装饰主菜和 Meal 基类亦即在运行时动态地替他们增加职责

{

//以父类声明的字段

//实现 UML Class Diagram 的 Aggregation指向 Meal 对象的指针

protected Meal meal;

public override string Description

{

get { return mealDescription + + + description; }

set { description = value; }

}

}

//副菜(装饰者)具体装饰类 面条用来动态装饰主菜 牛排或猪排

class Noodle : CondimentDecorator //类似前一例的 ConcreteDecoratorA

{

public Noodle(Meal meal) //构造函数

{

Description = 面条;

al = meal;

}

public override int cost() //具体装饰对象 A 的操作

{

return + st();

}

}

//副菜(装饰者)具体装饰类 沙拉用来动态装饰主菜 牛排或猪排

class Salad : CondimentDecorator //类似前一例的 ConcreteDecoratorB

{

public Salad(Meal meal) //构造函数

{

Description = 生菜沙拉;

al = meal;

}

public override int cost() //具体装饰对象 B 的操作

{

return + st();

}

}

//副菜(装饰者)具体装饰类 饮料用来动态装饰主菜 牛排或猪排

class Drink : CondimentDecorator //类似前一例的 ConcreteDecoratorC

{

private bool isHot;

public bool IsHot

{

get { return isHot; }

}

public Drink(Meal meal bool isHot) //构造函数

{

Description = (isHot) ? 热饮 : 冷饮;

al = meal;

thisisHot = isHot;

}

public override int cost() //具体装饰对象 C 的操作冷饮比热饮多收 块钱

{

return (isHot) ? ( + st()) : ( + st());

}

}

//副菜(装饰者)具体装饰类 甜点用来动态装饰主菜 牛排或猪排

class Dessert : CondimentDecorator //类似前一例的 ConcreteDecoratorD

{

public Dessert(Meal meal) //构造函数

{

Description = 甜点;

al = meal;

}

public override int cost() //具体装饰对象 D 的操作

{

return + st();

}

}

} // end of namespace

示例 _Steakaspxcs 的执行结果扩展功能时主菜和副菜可任意混合搭配使用

原理与本帖之前提过的相同将来店里若要推出 羊排鱼排…等新的主菜 (被装饰者) 时只要增加一个类去继承 Meal 这个抽象的基类即可若要推出新的副菜 (装饰者) 也一样只要增加一个类去继承 CondimentDecorator 抽象类即可我们透过这个重要的 CondimentDecorator 抽象类实现 UML 的 Aggregation (聚合) 观念以「装饰模式」取代传统「继承」的做法也同时降低了「主菜」「副菜」这两种类之间的耦合度

这个 CondimentDecorator 抽象类的作用还可将未来所有种类的副菜一些「共同的」行为或职责写在里面而且它还可用来区分哪些类是主菜哪些类是副菜因为主菜副菜都是继承自 Meal 这个最顶层的基类若没有这个 CondimentDecorator 抽象类将来 Class Diagram 变得很复杂时会不易分辨某个类是主菜或副菜

此外也因为主菜 (被装饰者)副菜 (装饰者) 都是继承自 Meal 这个最顶层的基类所以客户端程序 (Page_Load) 在引用他们的对象时把 new 出来的主菜实例new 出来的副菜实例再抛入其他副菜实例的构造函数中也会相容不会发生错误在相互引用和「运行」时期的「动态」扩展上变得很有弹性

最后补充一点Decorator 模式中装饰者类对象在组合时的「顺序」亦很重要本帖图 ConcreteDecoratorAConcreteDecoratorBConcreteDecoratorC 等具体装饰者在动态添加和组合时在某些实际应用中必须依照一定的顺序若顺序相反可能会导致程序执行结果不合理但本帖所提供的几个例子中并不需要考虑到这点

Decorator Pattern 适用的情景

你拥有一个已存在的组件类却无法继承它 (subclassing)

能够动态地为对象添加职责 (添加状态和行为)

改变类中的成员和行为但不影响其他对象

希望能便于职责的撤消

不想用「继承」来扩展行为其中一种原因是避免当一些功能要交叉搭配引用时单独用「继承」来设计会产生太多的子类太复杂的类图结构 [] []另一种考量可能是因为类的定义被隐藏或类的定义不能用于生成子类

Decorator Pattern 的优点

可避免单独使用「继承」时在扩展时不够弹性且可能衍生过多的子类

扩展时不需要修改既有的代码

可在执行时期动态地添加新行为 (职责)

Decorator Pattern 的缺点

可能会在程序中出现许多的小型类亦即需要编写很多 ConcreteDecorator 类 (具体装饰者)

若过度使用 Decorator 模式会让程序逻辑变得很复杂

别人较不易理解设计方式及代码排查故障追蹤和调试也比较困难

Decorator Pattern 的其他特性

每个要装饰的功能都放在单独的类中

我们可以用无数个装饰者去包装一个组件

「装饰者」可以在「被装饰者」前面或后面添加自己的行为甚至将「被装饰者」的行为整个取代掉以达到特定的目的

「被装饰者」并不需要知道它已经被「装饰」过了亦即 Component 类 (对象) 并不需要知道 Decorator 类 (对象) 的存在且 Decorator 也仅仅认识 Component

最后再补充在 Java I/ONET I/ONET SystemWindowsControls 命名空间中很多地方都有用到 Decorator 模式来设计 [] [] []例如我们查询 MSDN Library会发现 NET x 版本的 Windows Forms 其 API 里甚至还有增加一个新的 Decorator 类如下

SystemObject

SystemWindowsThreadingDispatcherObject

SystemWindowsDependencyObject

SystemWindowsMediaVisual

SystemWindowsUIElement

SystemWindowsFrameworkElement

SystemWindowsControlsDecorator

SystemWindowsControlsViewbox

SystemWindowsControlsDecorator 类

提供在单个子元素(如 Border 或 Viewbox)上或周围应用效果的元素的基类

cn/library/ntrolsdecoratoraspx

cn/library/ntrolsdecorator(VS)aspx

以下列出 Decorator Pattern用于实际程序的四种方式 []

装饰模式非常适合于「图形程序」亦适合于「视频和声音」比如视频流可用不同的比率进行压缩而声音可以输入给同声传译服务

装饰模式如此有用以致于 NET 已经有实际的 Decorator 类了如刚才提到的在 SystemWindowsControls 命名空间中提供了一些基类可用于给其他的图形元素添加特殊效果Border 类和 Viewbox 类

在如今的移动设备上Web 浏览器及其他应用中也大量运用 Decorator Pattern比如这些设备上可以创建适合小屏幕显示的对象跟标准桌面机上的浏览器相比这些屏幕可能也含有滚动条但不含有标题栏 []

从更一般的层次上NET 的 I/O API 中到处都是装饰模式 []如下方的体系

SystemObject

SystemMarshalByRefObject

SystemIOStream

SystemIOBufferedStream

SystemIOFileStream

SystemIOMemoryStream

SystemNetSocketsNetworkStream

SystemSecurityCryptographyCryptoStream

上方体系的最后五个子类有的就装饰了 Steram 这个父类 (如BufferedStream 类)因为它们从它继承同时又含有 Stream 类的实例而且这个实例是子类被构造时传递进去的 (如同本帖第二个西餐店的示例中副菜的构造函数的写法)如下方 MSDN Library 代码的 BufferedStream 类其构造函数的第一个参数即为其父类 SystemIOStream 的变量 stream

BufferedStream 构造函数

public BufferedStream(

Stream stream

               

上一篇:C# 语言规范--1.14 属性

下一篇:VC.NET索引对查询条件顺序的影响