装饰器模式在C#中通过接口+组合+委托实现,核心是不修改原类而动态添加行为;需定义统一接口(如ILogger),装饰器类组合该接口实例并转发调用,增强逻辑置于转发前后,支持链式组装与灵活扩展。

装饰器模式在 C# 里不是靠语法糖(比如 Python 的 @decorator)实现的,而是靠接口 + 组合 + 委托,核心是“不修改原类,动态添加行为”——如果你试图找 [Decorator] 特性或编译器支持,那会白忙活。
用接口定义可装饰的行为契约
装饰器本质是“同类型套一层”,所以必须有统一入口。比如你要装饰一个日志操作,先定义 ILogger 接口,所有具体实现(原始类、装饰器类)都实现它:
public interface ILogger
{
void Log(string message);
}
关键点:不能用抽象类代替接口——否则装饰器无法同时继承原始实现类和扩展逻辑;也不能让装饰器继承具体类(违反开闭原则)。常见错误是直接从 FileLogger 派生 TimestampLogger,结果只能装饰这一种,失去复用性。
写装饰器类:组合 + 转发 + 增强
装饰器本身是一个具体类,构造时接收一个 ILogger 实例(可以是原始实现,也可以是另一个装饰器),然后在自己的 Log 方法里做增强逻辑,再调用被装饰对象的 Log:
public class TimestampLogger : ILogger
{
private readonly ILogger _inner;
<pre class="brush:php;toolbar:false;">public TimestampLogger(ILogger inner) => _inner = inner;
public void Log(string message)
{
var stamped = $"[{DateTime.Now:HH:mm:ss}] {message}";
_inner.Log(stamped); // 转发给下一层
}
}
- 构造函数参数必须是接口类型(
ILogger),不是具体类(如ConsoleLogger) - 装饰器内部不 new 具体实现,靠外部注入——否则无法链式嵌套
- 增强逻辑放在转发前(前置处理)或后(后置处理),甚至可拦截不转发(比如权限装饰器直接 return)
运行时组装装饰器链,别硬编码
装饰器的价值在灵活组合。不要写 new TimestampLogger(new FileLogger()) 这种固定链;推荐用工厂方法或依赖注入容器管理:
// 手动组装示例(测试/简单场景)
ILogger logger = new TimestampLogger(
new RetryLogger(
new ConsoleLogger()));
使用 Microsoft.Extensions.DependencyInjection 时,注意注册顺序:最后注册的装饰器最先执行(因为它是最外层)。例如:
services.AddSingleton<ilogger consolelogger>(); services.Decorate<ilogger timestamplogger>(); // .AddDecorators 扩展需自行实现或引用 Scrutor</ilogger></ilogger>
没引入第三方库时,Decorate 不是内置方法——别搜 services.Decorate 报错原因,那是 Scrutor 库提供的扩展。
容易被忽略的性能与生命周期问题
每层装饰器都是一次对象分配和虚方法调用。如果日志高频(如每毫秒一次),堆栈过深(5 层+)可能引发 GC 压力或间接影响吞吐。更隐蔽的问题是:装饰器若持有 IDisposable 资源(比如打开文件流),必须确保整个链能正确释放——接口本身不继承 IDisposable,得自己约定或额外封装 IDisposableLogger。
另外,装饰器里的状态(比如计数器、缓存)是否该跨请求共享?多数场景应无状态,否则多线程下易出错;真要状态,得用 AsyncLocal 或明确标注线程安全。











