天天看点

设计模式(15)--结构型模式_装饰模式

作者:chencalf

0、主要参考

(1)讲的较好:https://blog.csdn.net/mzl87/article/details/127878302

(2)https://www.cnblogs.com/for-easy-fast/archive/2023/06/05/17458083.html

(3)也还行:https://www2014.aspxhtml.com/post-20244/

1、概念

1.1问题来源

(1)子类复子类,子类何其多。

设计模式(15)--结构型模式_装饰模式

图片来源https://www.bilibili.com/video/BV1Jz4y1d7TX?p=10

1.2装饰器模式的作用

装饰器模式旨在动态地向现有对象添加附加功能。装饰器为扩展功能提供了子类化的替代方法。虽然可以通过对象类的子类化来为整个对象类添加功能,但装饰器模式旨在仅向单个对象添加功能,而使该类的其他对象保持不变。

  • (1)有时有人评论说,通过在不修改原始类型的情况下增强现有类型,该模式遵循开闭原则。
  • (2)非常有趣的是,该模式通过违背OO建议的错误方式来实现其目标。

1.3组合与继承的选择?

面向对象设计的典型问题:

(1)对于给定的类/对象A,在创建要重用类/对象A的新类/对象B时,如何以及基于什么标准在组合和继承之间进行选择?通常给出的方案是:如果您打算重用现有类A的Public接口,请使用继承。如果您打算仅重用现有类A的功能,请选择组合。

然而装饰器模式正好相反:

它计划重用现有类的Public接口,但使用组合。这个决定对Public接口扩展的问题有一些影响,我们稍后会看到。

2 装饰器使用的动机和意图

2.1动机

子类复子类,子类何其多,功能扩展越来越复杂。上述描述的问题根源在于我们“过度地使用了继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀(多继承)。

如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?

2.2意图

动态地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类更灵活。(《设计模式》GoF)

2.3 用处

  • 在软件开发过程中,经常会遇到需要给一个对象添加一些额外的行为的情况。如果直接修改这个对象的代码,可能会引起其他代码的不稳定性,而且会导致代码的可维护性变差。此时,装饰器模式就可以很好地解决这个问题。
  • 使用装饰器模式,你可以在运行时动态地给一个对象添加一些额外的职责,而不需要修改这个对象的代码。这样,你就可以保持原有代码的稳定性和可维护性,同时还能够满足新的需求。

3 装饰器的实现

3.1装饰器模式的组成

装饰器模式的实现需要以下几个步骤:

  • (1)定义一个接口(或者抽象组件),它包含了被装饰对象和装饰器对象需要实现的方法。
  • (2)实现一个具体的类,它实现了这个接口,并且包含了一些基本的行为。
  • (3)实现一个装饰器类(这是一个抽象类),它也实现了这个接口,并且包含了一个指向被装饰对象的引用。
  • (4)在装饰器类中,实现需要添加的额外行为,并且在调用被装饰对象的方法时,也调用相应的方法。
  • (5)在客户端代码中,创建一个具体的对象,并且用装饰器类动态地给它添加一些额外的行为。

3.2装饰器的结构图

(1)

设计模式(15)--结构型模式_装饰模式

(2)

设计模式(15)--结构型模式_装饰模式

3.3测试例子

参考了https://www.cnblogs.com/for-easy-fast/archive/2023/06/05/17458083.html

(1)定义一个接口

比如定义个形状的类

// Component 接口
public interface IShape
{
    void Draw();
}           

(2)定义一个具体的类,实现了上面的接口

比如定义一个矩形的类的实现

// ConcreteComponent 类
public class Rectangle : IShape
{
    public void Draw()
    {
        Console.WriteLine("Drawing a rectangle.");
    }
}           

(3)定义装饰类,实现了上面的接口

注意:

  • 继承了接口,
  • 类里面有protected属性的接口字段
  • 使用虚函数virtual实现接口中的方法。(如果接口使用抽象类定义,则可以重写,并使用相同的方法实现)
// Decorator 类
public abstract class ShapeDecorator : IShape
{
    protected IShape decoratedShape;

    public ShapeDecorator(IShape decoratedShape)
    {
        this.decoratedShape = decoratedShape;
    }
    public virtual void Draw()
    {
        decoratedShape.Draw();
    }
}           

(4)定义2个具体的类,进行各自的功能扩展

//(4) ConcreteDecorator 类1
public class RedShapeDecorator : ShapeDecorator
{
    public RedShapeDecorator(IShape decoratedShape) : base(decoratedShape)
    {
    }

    public override void Draw()
    {
        decoratedShape.Draw();
       //具体功能扩展测试代码如下
        Console.WriteLine("Border Color: Red_1");
    }
}
//(5) ConcreteDecorator 类2
public class BlueShapeDecorator : ShapeDecorator
{
    public BlueShapeDecorator(IShape decoratedShape) : base(decoratedShape)
    {
    }

    public override void Draw()
    {
        decoratedShape.Draw();
      //具体功能扩展测试代码如下
        Console.WriteLine("Border Color: blue2_1");
    }
}
           

(5)客户端的测试代码

// 客户端代码
public class Client
{
    public void run()
    {
        // (1)创建一个具体的对象
        IShape rectangle = new Rectangle();

        // (2)用装饰器类动态地给它添加一些额外的行为
        IShape redRectangle = new RedShapeDecorator(rectangle);

        // (3)原始具体类的输出
        rectangle.Draw(); 
        Console.WriteLine("-----------------------------");
        // (4)用具体类装饰一下输出
        redRectangle.Draw();
        Console.WriteLine("-----------------------------");

        // (5)注意啦,对上面的装饰器结果接着进行装饰!!!
        IShape blueShapeDecorator = new BlueShapeDecorator(redRectangle);
        blueShapeDecorator.Draw();
    }
}

public class Demo
{
    static void Main(string[] args)
    {
        Client client = new Client();
        client.run();
    }

}           

(6)测试结果

设计模式(15)--结构型模式_装饰模式

4、相关过程和注意实现

(1)接口(抽象类)定义了规范

(2)具体类A实现了基本功能

(3)装饰器(抽象类)对接口进行了实现,并定义了protected的接口字段

(4)装饰器的具体类对接口功能进行了拓展

(5)客户端通过创建具体类A;在装饰器具体类A中,并在构造函数传入具体类A;从而拓展了具体类A的扩展功能

(6)客户端还可以创建装饰器具体类B,并在构造函数传入装饰器具体类A,接着扩展了装饰器A,从而实现了2个扩展功能

(7)装饰器可以一路装饰下去。

继续阅读