介绍
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
动机
在软件系统中,要给某个类型或者对象增加功能,如果使用“继承”的方案来写代码,就会出现子类暴涨的情况。比如:IMarbleStyle
是大理石风格的一个功能,IKeepWarm
是保温的一个接口定义,IHouseSecurity
是房子安全的一个接口,就三个接口来说,House是我们房子,我们的房子要什么功能就实现什么接口,如果房子要的是复合功能,接口不同的组合就有不同的结果,这样就导致我们子类膨胀严重,如果需要在增加功能,子类会成指数增长。
这个问题的根源在于我们“过度地使用了继承来扩展对象的功能”,由于继承为类型引入的静态特质(所谓静态特质,就是说如果想要某种功能,我们必须在编译的时候就要定义这个类,这也是强类型语言的特点。静态,就是指在编译的时候要确定的东西;动态,是指运行时确定的东西),使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀(多继承)。
意图
动态地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式
比生成子类更为灵活。
结构图
角色
- 抽象构件角色(Component):给出一个抽象接口,以规范准备接收附加责任的对象。
- 具体构件角色(Concrete Component):定义一个将要接收附加责任的类。
- 装饰角色(Decorator):持有一个构件(Component)对象的实例,并实现一个与抽象构件接口一致的接口。
- 具体装饰角色(Concrete Decorator):负责给构件对象添加上附加的责任。
实现
1 | namespace 装饰模式的实现 |
要点
- 通过采用组合、而非继承的手法,Decorator模式实现了在运行时动态地扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了单独使用继承带来的灵活性差和多子类衍生问题。
Component
类在Decorator模式中充当抽象接口的角色,不应该去实现具体的行为。而且Decorator
类对于Component
类应该透明——换言之Component
类无需知道Decorator
类,Decorator
类是从外部来扩展Component
类的功能。Decorator
类在接口上表现为is-a
Component
的继承关系,即Decorator
类继承了Component
类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator
类又使用了另外一个Component
类。我们可以使用一个或者多个Decorator
对象来“装饰”一个Component
对象,且装饰后的对象仍然是一个Component
对象。- Decorator模式并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决主体类在多个方向上的扩展功能——是为“装饰”的含义。
优缺点
优点
- 把抽象接口与其实现解耦。
- 抽象和实现可以独立扩展,不会影响到对方。
- 实现细节对客户透明,对用于隐藏了具体实现细节。
缺点
- 增加了系统的复杂度
使用场景
- 如果一个系统需要在构件的抽象化角色和具体化角色之间添加更多的灵活性,避免在两个层次之间建立静态的联系。
- 设计要求实现化角色的任何改变不应当影响客户端,或者实现化角色的改变对客户端是完全透明的。
- 需要跨越多个平台的图形和窗口系统上。
- 一个类存在两个独立变化的维度,且两个维度都需要进行扩展。
.net中的实现
在Net框架中,有一个类型很明显的使用了“装饰模式”,这个类型就是Stream
。Stream
类型是一个抽象接口,它在System.IO
命名空间里面,它其实就是Component
。FileStream
、NetworkStream
、MemoryStream
都是实体类ConcreteComponent
。右边的BufferedStream
、CryptoStream
是装饰对象,它们都是继承了Stream
接口的。