1. 装饰模式的引入
- 装饰模式以对客户端透明的方式扩展对象的功能,是一种用于替代继承的技术,它通过一种无须定义子类的方式给对象动态增加职责,使用对象之间的关联(组合)关系取代类之间的继承关系。
- 不能过度地使用继承来扩展对象的功能
- 静态特质,是指如果想要某种功能,必须在编译的时候就要定义这个类,这也是强类型语言的特点;
- 静态,就是指在编译的时候要确定的东西;
- 动态,是指运行时确定的东西;----更加灵活
- 由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性,并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
- 静态特质,是指如果想要某种功能,必须在编译的时候就要定义这个类,这也是强类型语言的特点;
动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
——《设计模式》GoF
- 以对客户透明的方式动态地给一个对象附加上更多的职责;
- 可以在不需要创建更多子类的情况下,让对象的功能得以扩展
2. 装饰模式的结构
- 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象—具体构件角色和装饰角色共同的接口。
- 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类—完成具体业务逻辑。
- 抽象装饰(Decorator)角色:持有一个构件(Component ->具体构件或具体装饰对象)对象的实例,并定义一个与抽象构件接口一致的接口。
- 具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加的责任。
需要注意的是Decorator和Component的关系,首先是继承关系(这个Is-A关系通常是接口继承,而不是类继承),然后是Decorator里有一个Component的关系(Has-A关系)。拥有这两重关系的优势就是能够组合起来,让它可以不断地扩展,把一个个装饰对象串起来。
抽象构件类典型代码:
abstract class Component
{
public abstract void Operation();
}
具体构件类典型代码:
class ConcreteComponent : Component
{
public override void Operation()
{
//基本功能实现--与业务逻辑有关
}
}
抽象装饰类典型代码:
class Decorator : Component{
private Component component; //维持一个对抽象构件对象的引用
//注入一个抽象构件类型的对象—也可以使用专门的方法setComponent()
public Decorator(Component component){
this.component = component;
}
public override void Operation(){
component.Operation(); //调用原有业务方法
}
}
具体装饰类典型代码:
class ConcreteDecorator : Decorator{
public ConcreteDecorator(Component component) : base(component){}
public override void Operation() //装饰的顺序可以在此调整
{
base.Operation(); //调用原有业务方法—串联所有装饰对象
AddedBehavior(); //调用新增业务方法
}
//新增业务方法
public void AddedBehavior() {
//功能扩展
}
}
典型的装饰过程(客户端代码):
new ConcreteDecorator1(
new ConcreteDecorator2(
new ConcreteDecorator3(
//最内层的对象最先被调用
new ConcreteComponent()))).Operation();
顺序:当对最外层的装饰对象调用Operation()函数时,将依次先调用base.Operation()函数,然后才执行本身新增的业务逻辑函数AddedBehavior();因此,最先调用具体构件的Operation()函数,其他装饰对象的Operation()函数将会按照装饰的次序先后被调用到。
3. 装饰模式的总结
- Decorator模式并非解决“多子类衍生的多继承”问题,Decorator模式,应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
- 装饰模式的优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合(内嵌装饰对象的顺序),设计者可以创造出很多不同行为的组合。
- 装饰模式的缺点:
使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。 - 装饰模式实现的讨论——简化结构
大多数情况下,装饰模式的实现都比上面定义中给出的示例的实现要简单。对模式进行简化时需要注意以下的情况:- 一个装饰类的接口必须与被装饰类的接口相容。
- 尽量保持Component作为一个“轻“的类,不要把太多的逻辑和状态放在Component类里。
- 如果只有一个ConcreteComponent类而没有抽象的Component类(接口),那么Decorator类经常可以是ConcreteComponent的一个子类(合并Component 和ConcreteComponent 角色为一个类)。
- 示例:PersonFinery
namespace PersonFinery{
// 个人信息类--合并了的Component和ConcreteComponent
class Person{
public Person(){}
//被装扮者的姓名
private string Name;
public Person(string name){Name = name;}
//装扮操作--处理具体业务逻辑
public virtual void Show(){
Console.Write("{0}的装扮---", Name);
}
}
}
namespace PersonFinery{
// Decrator类--抽象装饰角色
class Finery:Person{
///内嵌的装饰对象--具体的被装饰对象 或 具体的装饰对象
protected Person component;
/// 设置被装饰对象 或 具体的装饰对象--即SetComponent()功能
/// <param name="component">具体的被装饰对象 或 具体的装饰对象</param>
public void Decorate(Person component){
this.component = component;
}
/// 调用内嵌对象实现“装饰”
public override void Show(){
if (component != null)
component.Show();
}
}
}
namespace PersonFinery{
// 具体装饰类--大T恤,其他的具体装饰类 类似
class TShirts:Finery{
public override void Show()
{
base.Show();
Console.Write("大T恤 "); //具体装饰
}
}
}
namespace PersonFinery{
class Program{
static void Main(string[] args)
{
Person person = new Person("张三");//最底层被装扮的对象
Console.WriteLine("\n第1种装扮:");
//创建装扮对象
Finery tshirts = new TShirts();
Finery bigTrouser = new BigTrouser();
Finery shabbyShoes = new ShabbyShoes();
//建立装扮链表
tshirts.Decorate(person);
bigTrouser.Decorate(tshirts);
shabbyShoes.Decorate(bigTrouser);
//层层装扮!
shabbyShoes.Show(); //这些具体装饰类每次都要调用基类的show方法,当他们串成链后,最先执行的show应该是最先被装饰的那个类,即T恤衫先被打印
Console.WriteLine("\n第2种装扮:");
Finery suit = new Suit();
Finery neckwear = new Neckwear();
Finery leatherShoes = new LeatherShoes();
suit.Decorate(person);
neckwear.Decorate(suit);
leatherShoes.Decorate(neckwear);
leatherShoes.Show();
Console.Read();
}
}
}
执行结果:
第1种装扮:
张三的装扮---大T恤 垮裤 破球鞋
第2种装扮:
张三的装扮---西装 领带 皮鞋
4. 另一个装饰模式的demo
某软件公司基于面向对象技术开发了一套图形界面构件库——VisualComponent,该构件库提供了大量基本构件,如窗体、文本框、列表框等,由于在使用该构件库时,用户经常要求定制一些特殊的显示效果,如带滚动条的窗体、带黑色边框的文本框、既带滚动条又带黑色边框的列表框等等,因此经常需要对该构件库进行扩展以增强其功能。
使用装饰模式来设计该图形界面构件库。
(1) VisualComponent:抽象界面构件类,抽象构件角色
(2) Window: 窗体类, 具体构件角色
(3) TextBox:文本框类,具体构件角色
(4) ListBox:列表框类,具体构件角色
(5) ComponentDecorator:构件装饰类,抽象装饰角色
(6) ScrollBarDecorator:滚动条装饰类,具体装饰角色
(7) BlackBorderDecorator:黑色边框装饰类,具体装饰角色
(8) Program:客户端程序
namespace DecoratorSample{
class ListBox : VisualComponent{
public override void Display(){
Console.WriteLine("显示列表框!");}
}
}
namespace DecoratorSample{
class TextBox : VisualComponent{
public override void Display(){
Console.WriteLine("显示文本框!");}
}
}
namespace DecoratorSample{
class Window : VisualComponent{
public override void Display(){
Console.WriteLine("显示窗体!");}
}
}
namespace DecoratorSample{
// 抽象构件
abstract class VisualComponent{
public abstract void Display();
}
}
namespace DecoratorSample{
class BlackBorderDecorator : ComponentDecorator{
public BlackBorderDecorator(VisualComponent component)
: base(component){}
public override void Display(){
base.Display(); //后调用原有功能
this.SetBlackBorder(); //先加装饰
//base.Display(); //后调用原有功能
}
//实现具体装饰
public void SetBlackBorder()
{
Console.WriteLine("为构件增加黑色边框!");
}
}
}
namespace DecoratorSample{
class ScrollBarDecorator : ComponentDecorator{
public ScrollBarDecorator(VisualComponent component)
: base(component){}
public override void Display(){
base.Display(); //后调用原有功能
this.SetScrollBar(); //先加装饰
//base.Display(); //后调用原有功能
}
//实现具体装饰
public void SetScrollBar()
{
Console.WriteLine("为构件增加滚动条!");
}
}
}
namespace DecoratorSample{
// 抽象Decorator
class ComponentDecorator : VisualComponent{
private VisualComponent component; //维持对抽象构件类型对象的引用
//注入抽象构件类型的对象
public ComponentDecorator(VisualComponent component){
this.component = component;
}
public override void Display(){
component.Display();
}
}
}
namespace DecoratorSample{
class Program{
static void Main(string[] args){
VisualComponent component, componentSB, componentBB; //使用抽象构件定义
component = new Window(); //定义具体构件
componentSB = new ScrollBarDecorator(component); //定义装饰后的构件
componentBB = new BlackBorderDecorator(componentSB); //将装饰了一次之后的对象继续注入到另一个装饰类中,进行第二次装饰
componentBB.Display();
Console.Read();
}
}
}