模式简介

装饰器模式(Decorator Pattern):在不改变现有对象结构的情况下,动态地给对象增加一些额外的职责,即给现有对象增加一些新的功能。装饰器对客户是透明的,要让客户感受不到该对象被装饰过。因此,装饰器必须要实现被装饰对象的接口方法,从而保证对象的用法不变,不然对象的结构就被改变了。装饰器模式别名又称包装器(Wrapper),与适配器模式别名相同,它属于对象结构型模式。

模式结构

装饰器模式包含4个角色:

1、抽象构件(Component):定义一个抽象接口制定被装饰对象的规范。

2、具体构件(Concrete Component):实现抽象构件的抽象接口,是被装饰的直接对象。

3、抽象装饰器(Decorator):继承自抽象构件接口,并关联抽象构件对象,通过其子类装饰具体构件。

4、具体装饰器(Concrete Decorator):实现抽象装饰器方法,并对具体构件进行装饰,即为具体构件对象添加一些职责。

示例类图

下面以武器中的枪为例,分别通过具体的装饰器把枪装饰为AK47和M4A1。首先分析示例中的模式角色,武器(Weapon)视为抽象构件接口,枪(Gun)视为具体构件类。而AK47和M4A1分别由AK47Decorator类和M4A1Decorator类装饰而来,最后再从两个装饰器中抽象出一个抽象装饰器Decorator类。模式角色分析完毕,该示例的UML类图如下:

示例代码

1、定义抽象构件武器接口,制定被装饰对象的规范。

1
2
3
4
public interface Weapon
{
    void Fire();
}

2、定义具体构件枪类,实现武器接口,枪类对象为被装饰对象。

1
2
3
4
5
6
7
public class Gun: Weapon
{
    public void Fire()
    {
        Console.WriteLine("Shooting");
    }
}

3、定义抽象装饰器类,继承自抽象构件武器接口,并给具体装饰器留下装饰接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Decorator: Weapon
{
    private Weapon Weapon;

    public Decorator(Weapon Weapon)
    {
        this.Weapon = Weapon;
    }

    public virtual void Fire()
    {
        Weapon.Fire();
    }
}

4、定义具体装饰器AK47Decorator类,继承自抽象装饰器类,并重写抽象装饰器中的虚方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class AK47Decorator: Decorator
{
    // 由于抽象装饰器类使用有参构造函数,因此子类构造函数必须加base关键字并传入参数。
    public AK47Decorator(Weapon Weapon): base(Weapon)
    {
    
    }

    public override void Fire()
    {
        base.Fire();
        AK47Func();
    }

    public void AK47Func()
    {
        Console.WriteLine("AK47 Shooting");
    }
}

5、定义具体装饰器M4A1Decorator类,继承自抽象装饰器类,并重写抽象装饰器中的虚方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class M4A1Decorator: Decorator
{
    public M4A1Decorator(Weapon Weapon) : base(Weapon)
    {

    }

    public override void Fire()
    {
        base.Fire();
        M4A1Func();
    }

    public void M4A1Func()
    {
        Console.WriteLine("M4A1 Shooting");
    }
}

6、定义一个客户端Client类使用该装饰器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class client
{
    public static void Main(string[] args)
    {
        Weapon gun = new Gun();
        Weapon ak47 = new AK47Decorator(gun);
        Weapon m4a1 = new M4A1Decorator(gun);
        ak47.Fire();
        m4a1.Fire();
    }
}

7、输出结果:

1
2
3
4
// Shooting
// AK47 Shooting
// Shooting
// M4A1 Shooting

应用场景

装饰器模式主要用于,当需要给一个现有类添加额外职责时,而又不能采用继承生成子类的方式的情况。例如,该类被密封(sealed)或是终极类(final),或者通过继承的方式会生成大量子类,使系统变得更复杂。

此外,如果需要将现有的一些功能进行排列组合产生非常多的功能,使用继承会很麻烦。而使用装饰器通过关联的方式很容易实现。由于这种添加额外的功能方式是动态的,因此装饰器模式还可用于动态地为对象添加功能。游戏中经常有遇到各种对象,然后经过装饰器装饰后就增加了额外的属性和方法。

模式总结

模式优点

装饰器模式通过关联的方式动态地为现有对象添加了额外职责(功能),比继承方式更加灵活,类之间的耦合度也降低了。这充分体现了组合/聚合复用原则中“尽量使用关联而不是继承”的理念。通过继承为对象添加功能的方式是静态的,不能控制为对象添加功能的方式和时机,类之间耦合也更深。此外,新建一个具体装饰器十分方便,多个装饰器可以装饰出不同的对象,符合开闭原则。

模式缺点

过多使用装饰器模式会在系统中创建出很多小对象,增大了系统的复杂度。此外,由于装饰器模式比继承方式更灵活也更容易出错,如果装饰层级过多,一旦发生错误,排查错误就是一件很麻烦的事。

模式比较

装饰器模式和适配器模式很相似,甚至别名也相同。但二者的侧重点不同,适配器模式是将已有类的接口转换成另一个类能使用的接口,侧重于互不兼容的两个接口之间的转换逻辑。而装饰器模式是给已有对象动态地添加新功能,不会改变或已有对象的内部接口方法,侧重于添加新的功能。