装饰器模式

简介

装饰器模式(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,以及具体的装饰器类 ChristmasDecoratorBirthdayDecorator。在 Main 方法中,首先创建了一个简单的客厅对象,然后分别使用圣诞和生日装饰器来动态添加装饰物,并显示客厅的状态。

类图

@startuml

interface ILivingRoom {
    {abstract} +Display()
}

class SimpleLivingRoom {
    +Display()
}

abstract class LivingRoomDecorator {
    -livingRoom: ILivingRoom
    +LivingRoomDecorator(livingRoom: ILivingRoom)
    +Display()
}

class ChristmasDecorator {
    +ChristmasDecorator(livingRoom: ILivingRoom)
    +Display()
}

class BirthdayDecorator {
    +BirthdayDecorator(livingRoom: ILivingRoom)
    +Display()
}

ILivingRoom <|.. SimpleLivingRoom
ILivingRoom <|.. LivingRoomDecorator
LivingRoomDecorator <|.. ChristmasDecorator
LivingRoomDecorator <|.. BirthdayDecorator



@enduml
类图代码

实际案例

可以使用装饰器模式来构建中间件管道,以实现灵活的功能组合。通过这种方式,你可以将多个中间件组合起来,每个中间件负责一个特定的功能,从而实现复杂的请求处理逻辑。

以下是一个简单的示例,演示了如何使用装饰器模式来构建中间件管道:

假设有三个中间件,分别是记录请求处理时间的中间件、记录请求日志的中间件和处理请求的中间件。你可以按照以下方式组合这些中间件:

  1. 记录请求处理时间的中间件(Decorator 1)
  2. 记录请求日志的中间件(Decorator 2)
  3. 处理请求的中间件(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!");
            });
        });
    }
}

在这个示例中,RequestTimingMiddlewareRequestLoggingMiddleware 分别充当了装饰器的角色,用于添加请求处理时间记录和请求日志记录功能。RequestHandlerMiddleware 是处理请求的具体组件。

Startup 类的 Configure 方法中,我们通过调用 UseRequestTimingUseRequestLogging 扩展方法来注册这两个中间件,然后再注册处理请求的中间件。这样,每个请求都会依次经过这三个中间件,从而实现请求处理时间记录和请求日志记录的功能。

其他案例

  1. IO流处理:C# 中的 Stream 类就是一个典型的使用装饰器模式的例子。例如,BufferedStreamCryptoStreamFileStream 等都是 Stream 的装饰器,它们分别添加了缓冲、加密和文件操作等额外功能。

  2. WCF 中的消息处理器:Windows Communication Foundation(WCF)中的消息处理器也是使用装饰器模式的典型例子。消息处理器可以在传输和消息协议之上提供额外的功能,比如安全性、日志记录等。

  3. 数据库访问层:在数据访问层中,可以使用装饰器模式来动态地添加日志记录、性能监控、缓存等功能,而不需要修改原始的数据访问逻辑。

  4. 日志记录:日志记录是应用程序中常见的功能,可以使用装饰器模式来动态地给对象添加日志记录功能,而不需要修改原始的业务逻辑。

优点

  1. 灵活性:装饰器模式允许动态地给对象添加功能,而无需修改其原始类。你可以根据需要选择添加哪些装饰器,以及以何种顺序来添加,从而实现灵活的功能组合。

  2. 遵循开闭原则:通过装饰器模式,可以轻松地扩展对象的功能,而不需要修改现有的代码。这使得系统更容易维护和扩展,符合开闭原则。

  3. 单一职责原则:每个装饰器类通常只负责给对象添加一个特定的功能,这有助于保持类的单一职责,提高代码的可读性和可维护性。

  4. 组合功能:装饰器模式允许组合多个装饰器,从而实现复杂的功能组合,而不会造成类层次结构的爆炸性增长。

缺点

  1. 复杂性:如果过度使用装饰器模式,可能会导致系统中出现大量的装饰器类,增加了代码的复杂性和理解难度。

  2. 顺序依赖性:装饰器模式的功能依赖于装饰器的添加顺序,如果顺序不正确,可能会影响到最终的功能组合。这需要开发人员仔细考虑装饰器的顺序。

  3. 性能开销:每个装饰器都会增加额外的处理逻辑,可能会带来一定的性能开销。在性能要求较高的场景下,需要谨慎使用装饰器模式。

综上所述,虽然装饰器模式具有灵活性和扩展性等优点,但在设计时需要权衡好其复杂性和性能开销,避免过度使用。

适用场景

  1. 动态添加功能:当需要动态地给对象添加额外的功能或责任,并且希望这些功能能够灵活组合和扩展时,装饰器模式是一个很好的选择。它允许你在运行时动态地添加或删除功能,而不需要修改现有对象的代码。

  2. 避免类爆炸:当类的功能组合方式会导致类的数量呈指数级增长时,使用装饰器模式可以避免这种类爆炸的问题。通过装饰器模式,你可以将不同的功能拆分成独立的装饰器类,并根据需要组合这些装饰器类,从而减少类的数量。

  3. 单一职责原则:当需要遵循单一职责原则,确保每个类只负责一个功能时,装饰器模式可以帮助你实现这一目标。每个装饰器类通常只关注一个特定的功能,从而保持了类的单一职责。

  4. 不影响现有代码:当需要对现有代码进行功能扩展,但又不想修改现有代码时,装饰器模式可以派上用场。通过装饰器模式,你可以在不修改原始类的情况下,为对象动态添加新的功能,从而实现功能扩展。

  5. 保持接口一致性:当希望扩展对象的功能时,但又不想破坏对象原始接口时,装饰器模式可以很好地保持接口的一致性。由于装饰器类和被装饰类具有相同的接口,因此可以无缝地替换被装饰类。

 

posted @ 2024-02-28 14:40  咸鱼翻身?  阅读(5)  评论(0编辑  收藏  举报