简介
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向现有对象动态地添加新功能,同时又不改变其结构。该模式通过创建一个包装对象,也就是装饰器,来包裹原始对象,并在包裹的过程中添加新的行为或责任。
结构
- Component(组件):定义了一个对象接口,可以给这些对象动态地添加职责。
- ConcreteComponent(具体组件):实现了Component接口的具体对象,即被装饰的对象。
- Decorator(装饰器抽象类):持有一个Component对象的引用,并定义了与Component接口一致的接口。
- ConcreteDecorator(具体装饰器):实现了Decorator接口的具体装饰器,负责向Component对象添加新的责任。
案例
假设你有一个简单的客厅(SimpleLivingRoom
),里面只有一些基本的家具和装饰。然后你可以添加不同的装饰器来增添节日氛围,比如圣诞节你会加上圣诞树、彩灯等等,或者在生日派对时你会添加气球、彩带等装饰品。
这些装饰器(Decorator
)可以动态地叠加在客厅上,每一个装饰器负责添加一个特定的装饰。而客厅本身仍然保持不变,你可以根据需要添加或移除装饰器,使客厅适应不同的场合,而不需要修改客厅的原始结构。
下面是一个用 C# 实现装饰器模式的简单示例,展示了如何在客厅中动态添加装饰物:
using System; // Component interface ILivingRoom { void Display(); } // ConcreteComponent class SimpleLivingRoom : ILivingRoom { public void Display() { Console.WriteLine("This is a simple living room."); } } // Decorator abstract class LivingRoomDecorator : ILivingRoom { protected ILivingRoom livingRoom; public LivingRoomDecorator(ILivingRoom livingRoom) { this.livingRoom = livingRoom; } public virtual void Display() { livingRoom.Display(); } } // ConcreteDecorator class ChristmasDecorator : LivingRoomDecorator { public ChristmasDecorator(ILivingRoom livingRoom) : base(livingRoom) { } public override void Display() { base.Display(); Console.WriteLine("Adding Christmas decorations: Christmas tree, lights, etc."); } } // ConcreteDecorator class BirthdayDecorator : LivingRoomDecorator { public BirthdayDecorator(ILivingRoom livingRoom) : base(livingRoom) { } public override void Display() { base.Display(); Console.WriteLine("Adding birthday decorations: balloons, ribbons, etc."); } } class Program { static void Main(string[] args) { // 创建一个简单的客厅 ILivingRoom livingRoom = new SimpleLivingRoom(); Console.WriteLine("Simple Living Room:"); livingRoom.Display(); Console.WriteLine(); // 添加圣诞装饰 ILivingRoom christmasLivingRoom = new ChristmasDecorator(livingRoom); Console.WriteLine("Living Room Decorated for Christmas:"); christmasLivingRoom.Display(); Console.WriteLine(); // 添加生日装饰 ILivingRoom birthdayLivingRoom = new BirthdayDecorator(livingRoom); Console.WriteLine("Living Room Decorated for Birthday:"); birthdayLivingRoom.Display(); } }
这段代码首先定义了一个客厅接口 ILivingRoom
以及一个简单的客厅实现 SimpleLivingRoom
。然后定义了装饰器抽象类 LivingRoomDecorator
,以及具体的装饰器类 ChristmasDecorator
和 BirthdayDecorator
。在 Main
方法中,首先创建了一个简单的客厅对象,然后分别使用圣诞和生日装饰器来动态添加装饰物,并显示客厅的状态。
实际案例
可以使用装饰器模式来构建中间件管道,以实现灵活的功能组合。通过这种方式,你可以将多个中间件组合起来,每个中间件负责一个特定的功能,从而实现复杂的请求处理逻辑。
以下是一个简单的示例,演示了如何使用装饰器模式来构建中间件管道:
假设有三个中间件,分别是记录请求处理时间的中间件、记录请求日志的中间件和处理请求的中间件。你可以按照以下方式组合这些中间件:
- 记录请求处理时间的中间件(Decorator 1)
- 记录请求日志的中间件(Decorator 2)
- 处理请求的中间件(ConcreteComponent)
下面是一个简化的示例代码:
using System; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; // ConcreteComponent: 处理请求的中间件 public class RequestHandlerMiddleware { private readonly RequestDelegate _next; public RequestHandlerMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { // 处理请求 await _next(context); } } // Decorator 1: 记录请求处理时间的中间件 public class RequestTimingMiddleware { private readonly RequestDelegate _next; public RequestTimingMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { // 记录请求处理开始时间 var stopwatch = Stopwatch.StartNew(); // 调用下一个中间件 await _next(context); // 请求处理完成,计算处理时间并输出 stopwatch.Stop(); var elapsedTime = stopwatch.ElapsedMilliseconds; Console.WriteLine($"Request completed in {elapsedTime} ms"); } } // Decorator 2: 记录请求日志的中间件 public class RequestLoggingMiddleware { private readonly RequestDelegate _next; public RequestLoggingMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { // 记录请求日志 Console.WriteLine($"Request {context.Request.Path} received at {DateTime.Now}"); // 调用下一个中间件 await _next(context); } } // 中间件扩展方法 public static class MiddlewareExtensions { public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder builder) { return builder.UseMiddleware<RequestTimingMiddleware>(); } public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder) { return builder.UseMiddleware<RequestLoggingMiddleware>(); } } // Startup 类 public class Startup { public void Configure(IApplicationBuilder app) { // 构建中间件管道 app.UseRequestTiming(); app.UseRequestLogging(); app.UseMiddleware<RequestHandlerMiddleware>(); // 添加其他中间件和路由处理等 app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); }); }); } }
在这个示例中,RequestTimingMiddleware
和 RequestLoggingMiddleware
分别充当了装饰器的角色,用于添加请求处理时间记录和请求日志记录功能。RequestHandlerMiddleware
是处理请求的具体组件。
在 Startup
类的 Configure
方法中,我们通过调用 UseRequestTiming
和 UseRequestLogging
扩展方法来注册这两个中间件,然后再注册处理请求的中间件。这样,每个请求都会依次经过这三个中间件,从而实现请求处理时间记录和请求日志记录的功能。
其他案例
-
IO流处理:C# 中的
Stream
类就是一个典型的使用装饰器模式的例子。例如,BufferedStream
、CryptoStream
、FileStream
等都是Stream
的装饰器,它们分别添加了缓冲、加密和文件操作等额外功能。 -
WCF 中的消息处理器:Windows Communication Foundation(WCF)中的消息处理器也是使用装饰器模式的典型例子。消息处理器可以在传输和消息协议之上提供额外的功能,比如安全性、日志记录等。
-
数据库访问层:在数据访问层中,可以使用装饰器模式来动态地添加日志记录、性能监控、缓存等功能,而不需要修改原始的数据访问逻辑。
-
日志记录:日志记录是应用程序中常见的功能,可以使用装饰器模式来动态地给对象添加日志记录功能,而不需要修改原始的业务逻辑。
优点
-
灵活性:装饰器模式允许动态地给对象添加功能,而无需修改其原始类。你可以根据需要选择添加哪些装饰器,以及以何种顺序来添加,从而实现灵活的功能组合。
-
遵循开闭原则:通过装饰器模式,可以轻松地扩展对象的功能,而不需要修改现有的代码。这使得系统更容易维护和扩展,符合开闭原则。
-
单一职责原则:每个装饰器类通常只负责给对象添加一个特定的功能,这有助于保持类的单一职责,提高代码的可读性和可维护性。
-
组合功能:装饰器模式允许组合多个装饰器,从而实现复杂的功能组合,而不会造成类层次结构的爆炸性增长。
缺点
-
复杂性:如果过度使用装饰器模式,可能会导致系统中出现大量的装饰器类,增加了代码的复杂性和理解难度。
-
顺序依赖性:装饰器模式的功能依赖于装饰器的添加顺序,如果顺序不正确,可能会影响到最终的功能组合。这需要开发人员仔细考虑装饰器的顺序。
-
性能开销:每个装饰器都会增加额外的处理逻辑,可能会带来一定的性能开销。在性能要求较高的场景下,需要谨慎使用装饰器模式。
综上所述,虽然装饰器模式具有灵活性和扩展性等优点,但在设计时需要权衡好其复杂性和性能开销,避免过度使用。
适用场景
-
动态添加功能:当需要动态地给对象添加额外的功能或责任,并且希望这些功能能够灵活组合和扩展时,装饰器模式是一个很好的选择。它允许你在运行时动态地添加或删除功能,而不需要修改现有对象的代码。
-
避免类爆炸:当类的功能组合方式会导致类的数量呈指数级增长时,使用装饰器模式可以避免这种类爆炸的问题。通过装饰器模式,你可以将不同的功能拆分成独立的装饰器类,并根据需要组合这些装饰器类,从而减少类的数量。
-
单一职责原则:当需要遵循单一职责原则,确保每个类只负责一个功能时,装饰器模式可以帮助你实现这一目标。每个装饰器类通常只关注一个特定的功能,从而保持了类的单一职责。
-
不影响现有代码:当需要对现有代码进行功能扩展,但又不想修改现有代码时,装饰器模式可以派上用场。通过装饰器模式,你可以在不修改原始类的情况下,为对象动态添加新的功能,从而实现功能扩展。
-
保持接口一致性:当希望扩展对象的功能时,但又不想破坏对象原始接口时,装饰器模式可以很好地保持接口的一致性。由于装饰器类和被装饰类具有相同的接口,因此可以无缝地替换被装饰类。
标签:功能,中间件,模式,next,public,添加,装饰 From: https://www.cnblogs.com/mchao/p/18040337