Decorator Pattern(装饰器模式)

模式简介

       装饰器模式是一种结构型设计模式,适用于给一个现有的对象扩展新的功能,同时又不改变其原有的结构和行为。通常对于要给一个对象扩展新的功能,我们首先想到的方式是继承,而继承适合类的层次结构稳定的情况,而且是需要在编译时确定好层次结构,无法在运行时动态地添加功能。那么对于需要运行时动态添加功能、避免类爆炸或者结构不固定的需求,那么就可以考虑采用装饰器模式。

       常见的应用场景:动态功能扩展、避免过多的继承扩展、对现有对象的复用等。

装饰器模式与继承的对比
特性Decorator 模式继承
扩展功能允许在运行时动态添加或移除功能,避免类爆炸问题。静态定义,需要在编译时确定类的结构。
修改现有功能允许通过组合而非继承的方式修改现有对象的行为。通过覆写方法的方式修改现有类的行为。
开闭原则符合开闭原则,可以通过添加新的装饰器来扩展功能。在不修改现有类的情况下,很难添加新的功能。
灵活性提供更灵活的设计,允许动态地组合不同的功能。较为静态,类的结构在编译时已经确定,难以动态改变。
可读性可以更清晰地组织和理解不同功能的组合。需要谨慎设计继承结构,可能导致深层次的类层次结构不清晰。
代码重用允许在不修改现有类的情况下复用已有的功能。通过继承复用功能,但可能会导致父类的方法被不必要地继承。

模式结构

  1. Component(组件): 定义一个抽象接口,具体组件和装饰器都实现了这个接口。它是装饰器模式的核心,是被装饰的对象的抽象表示。

  2. ConcreteComponent(具体组件): 实现了 Component 接口的具体组件类,是被装饰的对象。

  3. Decorator(装饰器): 继承自 Component,并持有一个 Component 对象的引用。装饰器的接口与 Component 接口一致,但通常不直接使用 ConcreteComponent 类,而是通过组合的方式,将具体组件包装在装饰器中。

  4. ConcreteDecorator(具体装饰器): 实现了 Decorator 接口的具体装饰器类。它通过在调用父类的方法前后添加额外的行为,来扩展或修改原有组件的功能。

工作原理

  1. 客户端创建组件对象: 客户端创建一个具体组件对象,这通常是最初需要被装饰的对象。

  2. 创建装饰器对象: 客户端可以选择性地创建装饰器对象,这些装饰器对象实现了装饰器接口并包含一个指向具体组件对象的引用。

  3. 装饰器包装组件: 装饰器对象通过持有组件对象的引用,以组合的方式将具体组件包装在其中。

  4. 客户端使用装饰器: 客户端通过使用装饰器对象来调用组件的方法。装饰器对象在调用组件的方法之前或之后,可以添加额外的行为,从而扩展或修改组件的功能。

  5. 递归装饰: 如果需要多层装饰,装饰器可以递归地嵌套,每个装饰器都包装了上一层的装饰器或具体组件。

代码示例(C#)

提示:可在本栏目的资源篇“设计模式代码示例合集”下载所有完整代码资源。

武器:Weapon.cs


namespace DecoratorPattern;

// 武器
interface IWeapon
{
    string name { get; }
    void Operation();
}

// 剑
class Sword : IWeapon
{
    public string name => "Sword";

    public void Operation()
    {
        Console.WriteLine("Sword:This is a sword.");
    }
}

// 枪
class Gun : IWeapon
{
    public string name => "Gun";

    public void Operation()
    {
        Console.WriteLine("Gun:This is a gun.");
    }
}

装饰器:Decorator.cs


namespace DecoratorPattern;

// 武器装饰器
interface IWeaponDecorator : IWeapon { }

// 添加火焰属性
class FireDecorator : IWeaponDecorator
{
    public string name
    {
        get
        {
            if (weapon == null) return string.Empty;
            return weapon.name;
        }
    }

    private IWeapon weapon; // 武器

    public FireDecorator(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Operation()
    {
        Console.WriteLine($"Fire{name}:This is a {name} with the fire attribute.");
    }
}

// 添加红色属性
class RedDecorator : IWeaponDecorator
{
    public string name
    {
        get
        {
            if (weapon == null) return string.Empty;
            return weapon.name;
        }
    }

    private IWeapon weapon; // 武器

    public RedDecorator(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void Operation()
    {
        Console.WriteLine($"Red{name}:This is a {name}, and it's color is red.");
    }
}

测试代码:Program.cs

// ************* 8.装饰器模式测试 **************
using DecoratorPattern;

IWeapon sword = new Sword();
IWeapon gun = new Gun();

Console.WriteLine("不添加装饰器...");
sword.Operation();
gun.Operation();

Console.WriteLine("添加装饰器...");
IWeapon fireSword = new FireDecorator(sword);
fireSword.Operation();
IWeapon redGun = new RedDecorator(gun);
redGun.Operation();

代码解说

       上述代码简单演示了对武器添加属性或者添加颜色。通过火属性装饰器和红色装饰器分别为剑和枪扩展属性,而这种扩展是运行时动态添加的,并非是在编译时提前编写好的,装饰器并没有对武器原有的结构和行为进行改变。

       如果不采用装饰器模式,我们可以在定义武器时直接添加两个字段用于记录武器的特殊属性和颜色,或者通过继承的方式来扩展,但是当武器的参数或者类型过多时,就可能使得武器的结构变得臃肿和复杂,涉及到武器类型过多还可能因为继承导致类爆炸的问题,从而提升维护成本。所以除非武器的结构比较固定或者简单,否则如果后续需要扩展,尤其是对于武器某种复杂属性的扩展,还是建议通过装饰器模式来完成。

如果这篇文章对你有帮助,请给作者点个赞吧!

posted @ 2022-12-01 10:32  我与岁月的森林  阅读(19)  评论(0)    收藏  举报  来源