【读书活动感悟分享】—特能扛—《设计模式:可复用面向对象软件的基础》第九章 装饰模式 - Decorator(装饰)_文章

【读书活动感悟分享】—特能扛—《设计模式:可复用面向对象软件的基础》第九章 装饰模式 - Decorator(装饰)

程文庆
发表于 2025-11-10 18:04:28

概述定义

定义:
动态地给一个对象添加一些额外的职责,而不改变其原有类的结构。通过功能组合实现整体功能的动态增强,减少核心逻辑的重复代码。它是一种结构型模式,不需要在修改核心代码逻辑,只需要增加修饰类,常用于动态增强功能的场景。

目的:
装饰器模式在不改变核心代码的前提下,动态地扩展其功能,避免因功能组合过多而导致的类爆炸问题。它通过“包装”机制实现对对象行为的增强。

关键点:

  • 动态扩展:功能可以在运行时按需叠加,不需要改动原始类的代码,特别适合那些无法预知未来需求的场景。
  • 接口一致:装饰器和被装饰的对象对外暴露相同的接口,调用方完全无感,就像在用原来的对象一样。
  • 组合优于继承:通过持有对象引用(组合)来扩展行为,避免了继承带来的类爆炸问题,也更易于维护和复用。
  • 多层嵌套:可以像套娃一样叠加多个装饰器,每个只负责一个单一职责,组合起来就能实现复杂功能。
  • 开闭原则:要增加新功能只要写一个新的装饰器类就行,不用动已有的代码,真正做到“对扩展开放,对修改关闭”。

应用场景

  • UI组件增强:比如一个基础窗口,可以动态加上滚动条、边框、阴影等效果,每加一层都是一个装饰器,彼此独立又可自由组合。
  • 服务方法增强:给业务方法统一加上日志、权限检查或事务控制,而不用改动原有逻辑。比如调用某个服务前自动校验用户权限,之后记录操作日志,这些都可以通过装饰器实现。
  • 中间件系统:如 ASP.NET Core 的中间件管道本质上是装饰器链。
  • AOP 的底层思路:虽然 AOP 通常靠代理实现,但其思想和装饰器很接近——在不侵入原代码的前提下,在方法执行前后插入额外逻辑,比如性能监控、异常捕获等。

优缺点

优点

  • 扩展灵活:功能可以在运行时按需添加,不用像继承那样在编译期就定死。比如今天加个日志,明天加个缓存,随时组合,非常方便。
  • 避免类爆炸:如果没有装饰器,光是为了“带边框的滚动窗口”“带阴影的带边框窗口”这种组合,就得写一堆子类,名字都难起。用装饰器,每个功能独立成类,组合靠包装,清爽很多。
  • 符合开闭原则:要加新功能只要写一个新的装饰器就行,原来的类一行代码都不用动,既安全又省事。
  • 支持多层嵌套:可以一层套一层,比如先加密、再压缩、再加校验,每层只管自己的事,逻辑清晰,调试也容易。
  • 对调用方透明:无论对象被包装了多少层,外部使用方式完全不变,就像在用原始对象一样,学习和改造成本低。

缺点

  • 调试变麻烦:装饰链一旦套得太多,比如一层日志、一层缓存、一层权限、再加一层加密,调用栈就变得又深又绕,出问题时排查起来费劲。
  • 对象创建啰嗦:每次用都得手动一层层包,像 new A(new B(new C(base)))这种写法,代码又长又难看,尤其没有工厂辅助的时候,读起来很吃力。
  • 类数量膨胀:每个小功能都要单独写一个装饰器类,项目里类文件越来越多,管理起来有点琐碎,特别是功能粒度拆得太细的时候。
  • 容易过度设计:有时候只是加个简单逻辑,直接改原类几行代码就能搞定,非要用装饰器反而绕远了。不是所有扩展都值得上这个模式。
  • 性能有损耗:每多一层装饰,就多一次方法调用和对象跳转。在高频调用的路径上(比如每秒几万次的数据处理),这点开销积少成多。

实现示例

示例背景:读书系统

  • 基础书记:BookA、BookB
  • 可添加过程记笔记,复述文章:WriteNote、DecBook

抽象组件(ReadBooks)


public abstract class ReadBooks
{
    public string BookName { get; protected set; } = "";
    public abstract int GetReadBookPage();
    public override string ToString()
    {
        return $"{BookName} 读了 ({GetReadBookPage()}) 页";
    }
    public virtual string DescribeAction()
    {
        return $"{BookName} 读了 {GetReadBookPage()} 页";
    }
}

具体组件(BookA、BookB)



public class BookA : ReadBooks
{
    public BookA()
    {
        BookName = "水浒传";
    }

    public override int GetReadBookPage() => 30;

    public override string DescribeAction() => "读了水浒传30页";
}

public class BookB : ReadBooks 
{
    public BookB() 
    {
        BookName = "西游记";
    }

    public override int GetReadBookPage() => 50;

    public override string DescribeAction() => "读了西游记50页";
}

装饰器基类(ReadBookDecorator)


public abstract class ReadBookDecorator : ReadBooks
{
    protected ReadBooks _read;
    protected ReadBookDecorator(ReadBooks read)
    {
        _read = read;
    }
    public override string BookName => _read.BookName;
    public override int GetReadBookPage() => _read.GetReadBookPage();
}

具体装饰器(WriteNote、DecBook)


public class WriteNote : ReadBookDecorator
{
    public WriteNote(ReadBooks readBooks) : base(readBooks) { }

    public override string DescribeAction()
    {
        return _read.DescribeAction() + ",并做了笔记";
    }
}

public class ReTell : ReadBookDecorator
{
    public ReTell(ReadBooks readBooks) : base(readBooks) { }

    public override string DescribeAction()
    {
        return _read.DescribeAction() + ",并复述了内容";
    }
}

使用示例


class Program
{
    static void Main(string[] args)
    {
        ReadBooks book1 = new BookA();
        book1 = new WriteNote(book1);
        book1 = new ReTell(book1);  
        Console.WriteLine(book1.DescribeAction());

        Console.WriteLine(book1.ToString());
    }
}

总结思考

  • 它的核心就是通过包装对象来动态加功能,而不是靠改类或继承。把基础功能和增强逻辑拆开,用组合的方式一层层套上去,既灵活又不破坏原有结构。
  • 实现上一般分四块:一个统一的组件接口、具体的基础实现、装饰器的基类(持有组件引用)、以及一个个具体的装饰器(各自负责一种增强)。
  • 这种模式特别适合那些功能需要自由搭配、运行时才知道要加什么的场景,比如日志+权限+缓存的组合,或者UI控件叠加边框、滚动条等。
  • 不过得注意别套太多层,不然调用栈太深,debug 头疼。如果创建过程太啰嗦,可以配个工厂或构建器,让外面用起来干净利落。

总结:

我觉的装饰模式是为自己已有功能更好的进行扩展功能的一种方式。既 不影响原有功能,又向核心功能添加了新的包装,有效的把核心职责和装饰功能进行了分离,去除了相关类中重复的逻辑代码,适合功能可叠加的场景。








75 0

评论


意见反馈