装饰模式(Decorator)

发布时间 2023-10-18 17:01:39作者: huihui_teresa

定义

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。

装饰模式的结构和说明

  • Component:组件对象的接口,可以给这些对象动态地添加职责。
  • ConcreteComponent:具体的组件对象,实现组件对象接口,通常就是被装饰器装饰的原始对象,也就是可以给这个对象添加职责。
  • Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象。
  • ConcreteDecorator:实际的装饰器对象,实现具体要向被装饰对象添加的功能。

示例代码

public abstract class Component
{
    /// <summary>
    /// 示例方法
    /// </summary>
    public abstract void Operation();
}

/// <summary>
/// 具体实现组件对象接口的功能
/// </summary>
public class ConcreteComponent : Component
{
    public override void Operation()
    {
        //相应的功能处理
    }
}

/// <summary>
/// 装饰器接口,维持一个指向组件对象的接口对象,并定义一个与组件接口一致的接口
/// </summary>
public abstract class Decorator : Component
{
    //持有组件对象
    protected Component _component;

    protected Decorator(Component component)
    {
        _component = component;
    }

    public override void Operation()
    {
        //转发请求给组件对象,可以在转发前后执行一些附加动作
        _component.Operation();
    }
}

/// <summary>
/// 装饰器的具体实现对象,向组件对象添加职责
/// </summary>
public class ConcreteDecoratorA : Decorator
{
    public ConcreteDecoratorA(Component component) : base(component)
    {
    }

    /*
     * 添加的状态
     */
    private string addedState;

    public string GetAddedState()
    {
        return addedState;
    }

    public override void Operation()
    {
        //调用父类的方法,可以再调用前后执行一些附件的动作
        //在这里进行处理的时候,可以使用添加的状态
        base.Operation();
    }
}

/// <summary>
/// 装饰器的具体实现对象,向组件对象添加职责
/// </summary>
public class ConcreteDecoratorB : Decorator
{
    public ConcreteDecoratorB(Component component) : base(component)
    {
    }

    /// <summary>
    /// 需要添加的职责
    /// </summary>
    private void AdderBehavior()
    {
        //需要添加的职责实现
    }

    public override void Operation()
    {
        //调用父方法,可以在调用前后执行一些附加动作
        base.Operation();
        AdderBehavior();
    }
}

重写示例代码

  • 需要定义一个组件对象的接口,在这个接口中定义计算奖金的业务方法,因为外部就是使用这个接口来操作装饰模式构成的对象结构中的对象。
  • 需要添加一个基本的实现组件接口的对象,可以让它返回奖金为0就可以了。
  • 把各个计算奖金的规则当作装饰器对象,需要为它们定义一个统一的抽象的装饰器对象,方便约束各个具体的装饰器的接口。
  • 把各个计算奖金的规则实现成为具体的装饰器对象。

代码

/// <summary>
/// 计算奖金的组件接口
/// </summary>
public abstract class Component
{
    /// <summary>
    /// 计算某人在某段时间内的奖金,有些参数在演示中并不会使用
    /// 但在实际业务实现上是会用的,为了标识这个具体的业务方法
    /// 因此这些参数被保留了
    /// </summary>
    /// <param name="user"></param>
    /// <param name="begin"></param>
    /// <param name="end"></param>
    /// <returns></returns>
    public abstract double CalcPrize(string user, DateTime begin, DateTime end);
}

/// <summary>
/// 基本的实现计算奖金的类,也是被装饰器装饰的对象
/// </summary>
public class ConcrereComponent : Component
{
    public override double CalcPrize(string user, DateTime begin, DateTime end)
    {
        //只是一个默认的实现,默认没有奖金
        return 0;
    }
}

/// <summary>
/// 装饰器的接口,需要和被装饰的对象实现同样的接口
/// </summary>
public abstract class Decorator : Component
{
    //持有被装饰的组件对象
    protected Component _c;

    protected Decorator(Component c)
    {
        _c = c;
    }

    public override double CalcPrize(string user, DateTime begin, DateTime end)
    {
        //转调组件对象的方法
        return _c.CalcPrize(user, begin, end);
    }
}

/// <summary>
/// 装饰器对象,计算当月业务奖金
/// </summary>
public class MonthPrizeDecorator : Decorator
{
    public MonthPrizeDecorator(Component c) : base(c)
    {
    }

    public override double CalcPrize(string user, DateTime begin, DateTime end)
    {
        //先获取前面运算出来的奖金
        var money = base.CalcPrize(user, begin, end);
        //然后计算当月业务奖金,按人员和时间去获取当月业务额,然后再乘以3%
        var prize = 10000 * 0.03; //10000先写死了,可以去数据库获取
        Console.WriteLine($"{user}当月业务奖金{prize}");
        return money + prize;
    }
}

public class SumPrizeDecorator : Decorator
{
    public SumPrizeDecorator(Component c) : base(c)
    {
    }

    public override double CalcPrize(string user, DateTime begin, DateTime end)
    {
        //先获取前面运算出来的奖金
        var money = base.CalcPrize(user, begin, end);
        //:然后计算累计奖金,其实应按人员去获取累计的业务额,然后再乘以01%
        //简单演示一下,假定大家的累计业务额都是1000000 元
        var prize = 1000000 * 0.001;
        Console.WriteLine($"{user}累计奖金{prize}");
        return money + prize;
    }
}

public class GroupPrizeDecorator : Decorator
{
    public GroupPrizeDecorator(Component c) : base(c)
    {
    }

    public override double CalcPrize(string user, DateTime begin, DateTime end)
    {
        //先获取前面运算出来的奖金
        var money = base.CalcPrize(user, begin, end);
        //然后计算当月团队业务奖金,先计算出团队总的业务额,然后再乘以1%
        //假设都是一个团队的
        var group = 35000; //写死了,可以数据库获取
        var prize = group * 0.01;
        Console.WriteLine($"{user}当月团队业务奖金{prize}");
        return money + prize;
    }
}

代码调用

static void Main(string[] args)
{
    //先创建计算基本奖金的类,这也是被装饰的对象
    var c1 = new Decorator1.ConcrereComponent();

    //然后对计算的基本奖金进行装饰,这里要组合各个装饰
    //说明,各个装饰者之间最好是不要有先后顺序的限制
    //也就是先装饰谁和后装饰谁都应该是一样的

    //先组合普通业务人员的奖金计算
    var d1 = new MonthPrizeDecorator(c1);
    var d2 = new SumPrizeDecorator(d1);

    //注意:这里只需使用最后组合好的对象调用业务方法即可,会依次调用回去
    //日期对象都没有用上,所以传nul1就可以了
    var zs = d2.CalcPrize("张三", DateTime.Now, DateTime.Now);
    Console.WriteLine($"======张三应得奖金:{zs}");

    //如果是业务经理,还需要一个计算团队的奖金计算
    var d3 = new GroupPrizeDecorator(d2);
    var ww = d3.CalcPrize("王五", DateTime.Now, DateTime.Now);
    Console.WriteLine($"=======王经理应得奖金:{ww}");

    Console.ReadKey();
}

运行结果

aa

模式讲解

认识装饰模式

装饰模式能够实现动态地为对象添加功能,是从一个对象外部来给对象增加功能,相当于是改变了对象的外观。当装饰过后,从外部使用系统的角度看,就不再是使用原始的那个对象了,而是使用被一系列的装饰器装饰过后的对象。

这样就能够灵活地改变一个对象的功能,只要动态组合的装饰器发生了改变,那么最终所得到的对象的功能也就发生了改变。

变相地还得到了另外一个好处,那就是装饰器功能的复用,可以给一个对象多次增加同一个装饰器,也可以用同一个装饰器装饰不同的对象。

对象组合

现在在面向对象的设计中,有一条基本的规则就是“尽量使用对象组合,而不是对象继承”来扩展和复用功能。装饰模式的思考起点就是这个规则。

延伸

另外一点,各个装饰器之间最好是完全独立的功能,不要有依赖,这样在进行装饰组合的时候,才没有先后顺序的限制,也就是先装饰谁和后装饰谁都应该是一样的,否则会大大降低装饰器组合的灵活性。

装饰器和组件类的关系

装饰器是用来装饰组件的,装饰器一定要实现和组件类一致的接口,保证它们是同个类型,并具有同一个外观,这样组合完成的装饰才能够递归调用下去。

组件类是不知道装饰器的存在的,装饰器为组件添加功能是一种透明的包装,组件类毫不知情。需要改变的是外部使用组件类的地方,现在需要使用包装后的类,接口是一样的,但是具体的实现类发生了改变。

装饰模式和AOP

什么是AOP——面向方面编程

AOP是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。

从某个侧面来说,装饰模式和 AOP 要实现的功能是类似的,只不过 AOP 的实现方法不同,会更加灵活,更加可配置,另外 AOP 一个更重要的变化是思想上的变化一-“主从换位”,让原本主动调用的功能模块变成了被动等待,甚至在毫不知情的情况下被织入了很多新的功能。

装饰模式优缺点

  • 比继承更灵活
  • 更容易复用功能
  • 简化高层定义
  • 装饰模式的缺点是:会产生很多细粒度对象。

思考装饰模式

装饰模式的本质:动态组合。

何时选用装饰模式

  • 如果需要在不影响其他对象的情况下,以动态、透明的方式给对象添加职责,可以使用装饰模式,这几乎就是装饰模式的主要功能。
  • 如果不适合使用子类来进行扩展的时候,可以考虑使用装饰模式。因为装饰模式是使用的“对象组合”的方式。所谓不适合用子类扩展的方式,比如,扩展功能需要的子类太多,造成子类数目呈爆炸性增长。