必须用interface。装饰器模式要求统一接口(如IFileProcessor),所有装饰器和原始类均实现它,构造函数接收接口类型参数,通过_inner字段显式委托调用,以支持动态组合、运行时替换与前后逻辑插入。

装饰器模式在C#里到底要不要用interface?
必须用。装饰器本质是“对同一接口的多种实现做组合”,没有统一IDecorator或目标业务接口(比如IStream、IRepository),就无法在运行时透明替换和叠加。常见错误是直接继承原类——这会绑定具体类型,失去装饰器“动态组合”的意义。
实操建议:
- 先定义核心接口,如
IFileProcessor,所有原始类和装饰器都实现它 - 装饰器构造函数接收该接口类型参数(不是具体类),例如
public LoggingDecorator(IFileProcessor inner) - 避免让装饰器继承原始类(如
class LoggingDecorator : FileProcessor),否则破坏开闭原则且无法嵌套
Decorator类里怎么调用被包装对象?
靠构造时传入的inner字段,然后在每个方法里显式委托。这不是语法糖,是模式的关键控制点——你可以在调用前/后插入逻辑,也可以选择不调用(比如缓存命中时跳过)。
示例(简化):
public class RetryDecorator : IFileProcessor
{
private readonly IFileProcessor _inner;
public RetryDecorator(IFileProcessor inner) => _inner = inner;
public void Process(string path)
{
for (int i = 0; i < 3; i++)
{
try
{
_inner.Process(path); // 关键:显式委托
return;
}
catch (IOException)
{
if (i == 2) throw;
Thread.Sleep(100);
}
}
}
}
注意:_inner是只读字段,确保装饰链不可变;异常处理逻辑完全由装饰器掌控,原始FileProcessor对此一无所知。
多个装饰器叠加时顺序为什么重要?
因为调用是链式向内传递的,外层装饰器先执行自己的逻辑,再决定是否/何时调用_inner。顺序错了,日志可能记不到重试,缓存可能绕过权限校验。
典型错误写法:
// ❌ 错误:Logging包在最里层,重试失败时日志根本没机会打
var processor = new LoggingDecorator(
new RetryDecorator(
new FileProcessor()));
// ✅ 正确:Logging在外层,能记录整个重试过程
var processor = new LoggingDecorator(
new RetryDecorator(
new FileProcessor()));
实际组装推荐用工厂方法或DI容器注册,避免手写嵌套层级过深:
- 手动组合适合简单场景,但超过3层易出错
- ASP.NET Core中可用
AddTransient<IFileProcessor, FileProcessor>()+ 多个AddDecorator扩展方法 - 注意生命周期:若
_inner是Scoped而装饰器是Singleton,会引发异常
性能损耗和内存泄漏风险在哪?
每次装饰都会新增一层对象和虚方法调用,高频路径下不可忽视。更隐蔽的问题是:如果装饰器持有IDisposable资源(如FileStream),但没实现IDisposable并透传Dispose(),就会泄漏。
必须检查的点:
- 所有装饰器是否实现
IDisposable,并在Dispose()中调用_inner.Dispose() - 避免在装饰器中缓存大对象(如
byte[]),除非明确需要跨调用复用 - 异步方法(
ProcessAsync)要同样遵循委托模式,别漏掉await _inner.ProcessAsync(...)
装饰器不是万能胶,当扩展逻辑与核心职责强耦合(比如必须访问原始类私有字段),说明接口设计已失衡——这时该重构接口,而不是硬套装饰器。


















