掌握 C# 中的 Fluent Builder 模式:从基础到高级方案

作者:微信公众号:【架构师老卢】
2-4 17:32
23

流畅构建器模式(Fluent Builder pattern)是一种强大的设计模式,它通过更具可读性和可维护性的接口来创建复杂对象。本文将深入探讨如何在 C# 中实现流畅构建器模式,探索基本和高级场景,并分析 .NET 标准库中的实际示例。

为何使用流畅构建器模式?

在深入实现之前,让我们先了解一下为何要使用流畅构建器模式:

  • 通过方法链提高代码的可读性。
  • 将复杂对象的构建过程与其表示形式分离。
  • 在保持对象灵活构建的同时,强制实现不可变性。
  • 优雅地处理可选参数。
  • 为对象构建提供清晰的 API。

实际示例:HttpClient 配置

在 .NET 中,流畅构建器模式最常见的示例之一就是 HttpClientBuilder。让我们看看微软是如何实现它的:

var client = new HttpClient(new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(10),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
    MaxConnectionsPerServer = 10
});

虽然这样可以实现功能,但代码不够优雅。以下是我们如何为 HttpClient 实现一个流畅构建器:

public class HttpClientBuilder
{
    private readonly SocketsHttpHandler _handler;
    
    private HttpClientBuilder()
    {
        _handler = new SocketsHttpHandler();
    }
    
    public static HttpClientBuilder Create()
    {
        return new HttpClientBuilder();
    }
    
    public HttpClientBuilder WithConnectionLifetime(TimeSpan lifetime)
    {
        _handler.PooledConnectionLifetime = lifetime;
        return this;
    }
    
    public HttpClientBuilder WithIdleTimeout(TimeSpan timeout)
    {
        _handler.PooledConnectionIdleTimeout = timeout;
        return this;
    }
    
    public HttpClientBuilder WithMaxConnections(int maxConnections)
    {
        _handler.MaxConnectionsPerServer = maxConnections;
        return this;
    }
    
    public HttpClient Build()
    {
        return new HttpClient(_handler);
    }
}

现在,我们可以像这样创建一个 HttpClient

var client = HttpClientBuilder.Create()
    .WithConnectionLifetime(TimeSpan.FromMinutes(10))
    .WithIdleTimeout(TimeSpan.FromMinutes(5))
    .WithMaxConnections(10)
    .Build();

高级场景:带有配置上下文的嵌套构建器

让我们来看一个更复杂的涉及嵌套构建器的场景。假设我们正在为一个 Web 应用程序构建一个配置系统:

public class WebAppConfiguration
{
    public DatabaseSettings Database { get; }
    public CacheSettings Cache { get; }
    public AuthenticationSettings Authentication { get; }

    private WebAppConfiguration(WebAppConfigurationBuilder builder)
    {
        Database = builder.DatabaseBuilder.Build();
        Cache = builder.CacheBuilder.Build();
        Authentication = builder.AuthenticationBuilder.Build();
    }

    public class WebAppConfigurationBuilder
    {
        internal readonly DatabaseSettingsBuilder DatabaseBuilder;
        internal readonly CacheSettingsBuilder CacheBuilder;
        internal readonly AuthenticationSettingsBuilder AuthenticationBuilder;

        public WebAppConfigurationBuilder()
        {
            DatabaseBuilder = new DatabaseSettingsBuilder(this);
            CacheBuilder = new CacheSettingsBuilder(this);
            AuthenticationBuilder = new AuthenticationSettingsBuilder(this);
        }

        public DatabaseSettingsBuilder ConfigureDatabase()
        {
            return DatabaseBuilder;
        }

        public CacheSettingsBuilder ConfigureCache()
        {
            return CacheBuilder;
        }

        public AuthenticationSettingsBuilder ConfigureAuthentication()
        {
            return AuthenticationBuilder;
        }

        public WebAppConfiguration Build()
        {
            return new WebAppConfiguration(this);
        }
    }
}

public class DatabaseSettings
{
    public string ConnectionString { get; }
    public int MaxConnections { get; }
    public TimeSpan CommandTimeout { get; }

    internal DatabaseSettings(string connectionString, int maxConnections, TimeSpan commandTimeout)
    {
        ConnectionString = connectionString;
        MaxConnections = maxConnections;
        CommandTimeout = commandTimeout;
    }
}

public class DatabaseSettingsBuilder
{
    private readonly WebAppConfiguration.WebAppConfigurationBuilder _parentBuilder;
    private string _connectionString;
    private int _maxConnections;
    private TimeSpan _commandTimeout;

    internal DatabaseSettingsBuilder(WebAppConfiguration.WebAppConfigurationBuilder parentBuilder)
    {
        _parentBuilder = parentBuilder;
    }

    public DatabaseSettingsBuilder WithConnectionString(string connectionString)
    {
        _connectionString = connectionString;
        return this;
    }

    public DatabaseSettingsBuilder WithMaxConnections(int maxConnections)
    {
        _maxConnections = maxConnections;
        return this;
    }

    public DatabaseSettingsBuilder WithCommandTimeout(TimeSpan timeout)
    {
        _commandTimeout = timeout;
        return this;
    }

    public WebAppConfiguration.WebAppConfigurationBuilder Done()
    {
        return _parentBuilder;
    }

    internal DatabaseSettings Build()
    {
        return new DatabaseSettings(_connectionString, _maxConnections, _commandTimeout);
    }
}

public class CacheSettings
{
    public string RedisConnection { get; }
    public TimeSpan DefaultExpiration { get; }

    internal CacheSettings(string redisConnection, TimeSpan defaultExpiration)
    {
        RedisConnection = redisConnection;
        DefaultExpiration = defaultExpiration;
    }
}

public class CacheSettingsBuilder
{
    private readonly WebAppConfiguration.WebAppConfigurationBuilder _parentBuilder;
    private string _redisConnection;
    private TimeSpan _defaultExpiration;

    internal CacheSettingsBuilder(WebAppConfiguration.WebAppConfigurationBuilder parentBuilder)
    {
        _parentBuilder = parentBuilder;
    }

    public CacheSettingsBuilder WithRedisConnection(string redisConnection)
    {
        _redisConnection = redisConnection;
        return this;
    }

    public CacheSettingsBuilder WithDefaultExpiration(TimeSpan defaultExpiration)
    {
        _defaultExpiration = defaultExpiration;
        return this;
    }

    public WebAppConfiguration.WebAppConfigurationBuilder Done()
    {
        return _parentBuilder;
    }

    internal CacheSettings Build()
    {
        return new CacheSettings(_redisConnection, _defaultExpiration);
    }
}

public class AuthenticationSettings
{
    public string JwtSecret { get; }
    public TimeSpan TokenExpiration { get; }

    internal AuthenticationSettings(string jwtSecret, TimeSpan tokenExpiration)
    {
        JwtSecret = jwtSecret;
        TokenExpiration = tokenExpiration;
    }
}

public class AuthenticationSettingsBuilder
{
    private readonly WebAppConfiguration.WebAppConfigurationBuilder _parentBuilder;
    private string _jwtSecret;
    private TimeSpan _tokenExpiration;

    internal AuthenticationSettingsBuilder(WebAppConfiguration.WebAppConfigurationBuilder parentBuilder)
    {
        _parentBuilder = parentBuilder;
    }

    public AuthenticationSettingsBuilder WithJwtSecret(string jwtSecret)
    {
        _jwtSecret = jwtSecret;
        return this;
    }

    public AuthenticationSettingsBuilder WithTokenExpiration(TimeSpan tokenExpiration)
    {
        _tokenExpiration = tokenExpiration;
        return this;
    }

    public WebAppConfiguration.WebAppConfigurationBuilder Done()
    {
        return _parentBuilder;
    }

    internal AuthenticationSettings Build()
    {
        return new AuthenticationSettings(_jwtSecret, _tokenExpiration);
    }
}

这使得配置变得非常直观:

var config = new WebAppConfiguration.WebAppConfigurationBuilder()
    .ConfigureDatabase()
        .WithConnectionString("Server=myserver;Database=mydb")
        .WithMaxConnections(100)
        .WithCommandTimeout(TimeSpan.FromSeconds(30))
        .Done()
    .ConfigureCache()
        .WithRedisConnection("localhost:6379")
        .WithDefaultExpiration(TimeSpan.FromMinutes(10))
        .Done()
    .ConfigureAuthentication()
        .WithJwtSecret("your-secret-key")
        .WithTokenExpiration(TimeSpan.FromHours(1))
        .Done()
    .Build();

实际示例:StringBuilder

.NET 中的 StringBuilder 类是流畅构建器模式的另一个优秀示例:

var message = new StringBuilder()
    .Append("Hello")
    .Append(" ")
    .Append("World")
    .AppendLine("!")
    .ToString();

高级模式:带约束的泛型构建器

有时,你需要创建适用于泛型类型的构建器。以下是一个泛型集合构建器的示例:

public class CollectionBuilder<T> where T : class
{
    private readonly List<T> _items = new List<T>();
    private readonly List<Action<List<T>>> _configurations = new List<Action<List<T>>>();

    public CollectionBuilder<T> Add(T item)
    {
        _items.Add(item);
        return this;
    }

    public CollectionBuilder<T> AddRange(IEnumerable<T> items)
    {
        _items.AddRange(items);
        return this;
    }

    public CollectionBuilder<T> Configure(Action<List<T>> configuration)
    {
        _configurations.Add(configuration);
        return this;
    }

    public CollectionBuilder<T> WithFilter(Func<T, bool> predicate)
    {
        _configurations.Add(items => 
        {
            var itemsToRemove = items.Where(x => !predicate(x)).ToList();
            foreach (var item in itemsToRemove)
            {
                items.Remove(item);
            }
        });
        return this;
    }

    public IReadOnlyList<T> Build()
    {
        foreach (var configuration in _configurations)
        {
            configuration(_items);
        }
        return _items.AsReadOnly();
    }
}

使用示例:

var numbers = new CollectionBuilder<string>()
    .Add("1")
    .Add("2")
    .Add("3")
    .AddRange(new[] { "4", "5", "6" })
    .Configure(list => list.Sort())
    .WithFilter(x => int.Parse(x) > 2)
    .Build();

最佳实践和准则

在实现流畅构建器模式时,请考虑以下最佳实践:

  • 使最终对象不可变。
  • 使用以 “With” 或类似前缀开头的描述性方法名。
  • 从配置方法中返回构建器实例。
  • 提供一个清晰的 Build() 方法。
  • 考虑将构建器的构造函数设为私有,并提供一个静态创建方法。
  • 使用方法链来创建更自然的 API。
  • Build() 方法中实现验证。
  • 考虑添加一个 Reset() 方法以便重用构建器。

常见陷阱及避免方法

  • 过度复杂化简单对象的创建。
  • 未正确处理空值。
  • 在构建对象后使构建器可变。
  • 未实现适当的验证。
  • 创建过多的嵌套构建器。
  • 未提供重置构建器的方法。

何时使用流畅构建器模式

流畅构建器模式在以下情况下最为有用:

  • 对象的构建需要许多参数。
  • 对象的构建涉及多个步骤。
  • 需要强制实现不可变性。
  • 希望提供更具可读性的 API。
  • 需要构建带有可选参数的对象。
  • 处理复杂的嵌套对象。

流畅构建器模式是你 C# 工具包中的强大工具。如果实现得当,它可以显著提高代码的可读性和可维护性。通过研究 .NET 标准库中的实际示例并理解高级场景,你可以在代码中明智地决定何时以及如何实现该模式。

请记住,虽然该模式很强大,但对于简单对象的创建并非总是必要的。当提高可读性和可维护性的好处超过实现该模式所带来的额外复杂性时,再使用它。

相关留言评论
昵称:
邮箱:
阅读排行