建造者模式
大约 13 分钟约 3871 字
建造者模式
简介
建造者(Builder)将复杂对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示。理解建造者模式,有助于优雅地处理包含多个可选参数的复杂对象创建。
建造者模式的核心问题是如何优雅地创建一个"复杂"对象 —— 当一个对象有大量属性(特别是可选属性)时,传统的构造函数方式会导致参数列表过长、代码难以阅读。建造者模式通过"分步构建"的方式解决这个问题,每一步设置一个属性,最终调用 Build() 方法生成对象。
在 C# 生态中,建造者模式有多种表现形式:经典 Builder(带 Director)、Fluent Builder(链式调用)、以及 C# 12 引入的"主构造函数 + with 表达式"等。选择哪种形式取决于对象的复杂度和使用场景。
特点
结构分析
UML 类图
+--------------------+
| Director |
+--------------------+
| +Construct(builder)|
+--------------------+
+--------------------+
| IBuilder |
+--------------------+
| +BuildPartA() |
| +BuildPartB() |
| +GetResult() |
+--------------------+
^
|
+----------+----------+
| |
+-----------+ +-----------+
| ConcreteB | | ConcreteB |
| uilderA | | uilderB |
+-----------+ +-----------+Fluent Builder 调用链
Builder.Create()
.WithUrl("https://...")
.WithMethod("POST")
.WithBearerToken("xxx")
.WithBody("{...}")
.WithTimeout(10s)
.Build() --> HttpRequestConfig实现
Fluent Builder 链式构建
// 产品 — HTTP 请求配置
public class HttpRequestConfig
{
public string Url { get; init; } = "";
public string Method { get; init; } = "GET";
public Dictionary<string, string> Headers { get; init; } = new();
public Dictionary<string, string> QueryParams { get; init; } = new();
public string? Body { get; init; }
public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30);
public int RetryCount { get; init; }
public bool EnableCache { get; init; }
}
// Builder
public class HttpRequestBuilder
{
private string _url = "";
private string _method = "GET";
private readonly Dictionary<string, string> _headers = new();
private readonly Dictionary<string, string> _queryParams = new();
private string? _body;
private TimeSpan _timeout = TimeSpan.FromSeconds(30);
private int _retryCount;
private bool _enableCache;
public HttpRequestBuilder WithUrl(string url) { _url = url; return this; }
public HttpRequestBuilder WithMethod(string method) { _method = method; return this; }
public HttpRequestBuilder WithHeader(string key, string value) { _headers[key] = value; return this; }
public HttpRequestBuilder WithBearerToken(string token) { _headers["Authorization"] = $"Bearer {token}"; return this; }
public HttpRequestBuilder WithQueryParam(string key, string value) { _queryParams[key] = value; return this; }
public HttpRequestBuilder WithBody(string body) { _body = body; return this; }
public HttpRequestBuilder WithTimeout(TimeSpan timeout) { _timeout = timeout; return this; }
public HttpRequestBuilder WithRetry(int count) { _retryCount = count; return this; }
public HttpRequestBuilder WithCache(bool enable = true) { _enableCache = enable; return this; }
public HttpRequestConfig Build()
{
if (string.IsNullOrWhiteSpace(_url))
throw new InvalidOperationException("URL 不能为空");
return new HttpRequestConfig
{
Url = _url,
Method = _method,
Headers = new Dictionary<string, string>(_headers),
QueryParams = new Dictionary<string, string>(_queryParams),
Body = _body,
Timeout = _timeout,
RetryCount = _retryCount,
EnableCache = _enableCache
};
}
public static HttpRequestBuilder Create() => new();
}
// 链式使用
var config = HttpRequestBuilder.Create()
.WithUrl("https://api.example.com/users")
.WithMethod("POST")
.WithBearerToken("eyJhbGciOiJIUzI1NiIs...")
.WithHeader("Accept", "application/json")
.WithQueryParam("page", "1")
.WithBody("""{"name": "张三", "age": 25}""")
.WithTimeout(TimeSpan.FromSeconds(10))
.WithRetry(3)
.WithCache()
.Build();带 Director 的建造者
// 产品 — 报表
public class Report
{
public string Title { get; init; } = "";
public string Author { get; init; } = "";
public DateTime CreatedAt { get; init; }
public List<string> Sections { get; init; } = new();
public Dictionary<string, object> Metadata { get; init; } = new();
public string Footer { get; init; } = "";
}
// Builder 接口
public interface IReportBuilder
{
IReportBuilder SetTitle(string title);
IReportBuilder SetAuthor(string author);
IReportBuilder AddSection(string title, string content);
IReportBuilder AddMetadata(string key, object value);
IReportBuilder SetFooter(string footer);
Report Build();
}
// HTML 报告 Builder
public class HtmlReportBuilder : IReportBuilder
{
private string _title = "";
private string _author = "";
private readonly List<string> _sections = new();
private readonly Dictionary<string, object> _metadata = new();
private string _footer = "";
public IReportBuilder SetTitle(string title) { _title = title; return this; }
public IReportBuilder SetAuthor(string author) { _author = author; return this; }
public IReportBuilder AddSection(string title, string content)
{
_sections.Add($"<h2>{title}</h2><div>{content}</div>");
return this;
}
public IReportBuilder AddMetadata(string key, object value) { _metadata[key] = value; return this; }
public IReportBuilder SetFooter(string footer) { _footer = footer; return this; }
public Report Build() => new()
{
Title = $"<h1>{_title}</h1>",
Author = _author,
CreatedAt = DateTime.UtcNow,
Sections = _sections.ToList(),
Metadata = _metadata,
Footer = $"<footer>{_footer}</footer>"
};
}
// Director — 封装标准构建流程
public class ReportDirector
{
public Report BuildMonthlySalesReport(IReportBuilder builder, string month, decimal total)
{
return builder
.SetTitle($"月度销售报表 - {month}")
.SetAuthor("系统自动生成")
.AddSection("概述", $"本月销售总额: Y{total:N2}")
.AddSection("趋势分析", "环比增长 15%")
.AddMetadata("report_type", "monthly_sales")
.AddMetadata("period", month)
.SetFooter($"生成时间: {DateTime.UtcNow:yyyy-MM-dd HH:mm}")
.Build();
}
public Report BuildAnnualReport(IReportBuilder builder, int year)
{
return builder
.SetTitle($"年度综合报告 - {year}")
.SetAuthor("财务部")
.AddSection("年度摘要", $"本年度运营数据汇总")
.AddSection("财务分析", "详细财务数据分析")
.AddSection("展望", $"{year + 1} 年度规划")
.SetFooter($"机密文件 - {year}")
.Build();
}
}
// 使用
var director = new ReportDirector();
var report = director.BuildMonthlySalesReport(new HtmlReportBuilder(), "2026-03", 1250000m);Record 类型 Builder 模式
// C# 12 简化 Builder — 利用 record 的 with 表达式
public record EmailMessage(
string To,
string Subject,
string Body,
string? Cc = null,
string? Bcc = null,
string From = "noreply@example.com",
bool IsHtml = false,
List<string>? Attachments = null,
Dictionary<string, string>? Headers = null)
{
// 便捷工厂
public static EmailBuilder Create(string to, string subject) => new(to, subject);
}
public class EmailBuilder
{
private string _to;
private string _subject;
private string _body = "";
private string? _cc;
private string? _bcc;
private string _from = "noreply@example.com";
private bool _isHtml;
private List<string>? _attachments;
private Dictionary<string, string>? _headers;
public EmailBuilder(string to, string subject) { _to = to; _subject = subject; }
public EmailBuilder WithBody(string body) { _body = body; return this; }
public EmailBuilder WithCc(string cc) { _cc = cc; return this; }
public EmailBuilder WithBcc(string bcc) { _bcc = bcc; return this; }
public EmailBuilder From(string from) { _from = from; return this; }
public EmailBuilder AsHtml(bool isHtml = true) { _isHtml = isHtml; return this; }
public EmailBuilder WithAttachment(string path) { (_attachments ??= new()).Add(path); return this; }
public EmailMessage Build()
{
if (string.IsNullOrWhiteSpace(_to)) throw new ArgumentException("收件人不能为空");
return new EmailMessage(_to, _subject, _body, _cc, _bcc, _from, _isHtml, _attachments, _headers);
}
}
// 使用
var email = EmailMessage.Create("user@example.com", "通知")
.WithBody("<h1>欢迎</h1>")
.AsHtml()
.WithCc("admin@example.com")
.WithAttachment("/docs/report.pdf")
.Build();实战:数据库连接字符串 Builder
public class ConnectionStringBuilder
{
private string _server = "localhost";
private int _port = 1433;
private string _database = "";
private string? _username;
private string? _password;
private bool _integratedSecurity = true;
private int _connectTimeout = 30;
private bool _encrypt = true;
private string _trustServerCertificate = "true";
public ConnectionStringBuilder WithServer(string server) { _server = server; return this; }
public ConnectionStringBuilder WithPort(int port) { _port = port; return this; }
public ConnectionStringBuilder WithDatabase(string db) { _database = db; return this; }
public ConnectionStringBuilder WithCredentials(string user, string password)
{
_username = user; _password = password; _integratedSecurity = false; return this;
}
public ConnectionStringBuilder WithIntegratedSecurity() { _integratedSecurity = true; return this; }
public ConnectionStringBuilder WithTimeout(int seconds) { _connectTimeout = seconds; return this; }
public string Build()
{
if (string.IsNullOrEmpty(_database)) throw new ArgumentException("数据库名不能为空");
var parts = new List<string>
{
$"Server={_server},{_port}",
$"Database={_database}",
$"Integrated Security={(_integratedSecurity ? "SSPI" : "False")}",
$"Connect Timeout={_connectTimeout}",
$"Encrypt={(_encrypt ? "True" : "False")}",
$"TrustServerCertificate={_trustServerCertificate}"
};
if (!_integratedSecurity && !string.IsNullOrEmpty(_username))
{
parts.Add($"User Id={_username}");
parts.Add($"Password={_password}");
}
return string.Join(";", parts);
}
public static ConnectionStringBuilder Create() => new();
}
// 使用
var connStr = ConnectionStringBuilder.Create()
.WithServer("db.example.com")
.WithPort(5432)
.WithDatabase("MyApp")
.WithCredentials("admin", "P@ssw0rd")
.WithTimeout(60)
.Build();实战:泛型 Builder 基类(减少重复代码)
当项目中有多个 Builder 时,可以用泛型基类减少字段定义和赋值的重复代码。
// 泛型 Builder 基类 — 利用反射自动映射属性
public abstract class BuilderBase<TProduct, TBuilder> where TBuilder : BuilderBase<TProduct, TBuilder>
{
private readonly Dictionary<string, object?> _values = new();
protected TBuilder Set<TValue>(string propertyName, TValue value)
{
_values[propertyName] = value;
return (TBuilder)this;
}
protected TValue? Get<TValue>(string propertyName, TValue? defaultValue = default)
{
return _values.TryGetValue(propertyName, out var value) ? (TValue?)value : defaultValue;
}
public abstract TProduct Build();
protected void ApplyTo(TProduct product)
{
var type = typeof(TProduct);
foreach (var (key, value) in _values)
{
var prop = type.GetProperty(key);
if (prop?.CanWrite == true)
prop.SetValue(product, value);
}
}
}
// 具体 Builder 继承基类
public class AppSettingsBuilder : BuilderBase<AppSettings, AppSettingsBuilder>
{
public AppSettingsBuilder WithAppName(string name) => Set(nameof(AppSettings.AppName), name);
public AppSettingsBuilder WithVersion(string ver) => Set(nameof(AppSettings.Version), ver);
public AppSettingsBuilder WithDebug(bool debug) => Set(nameof(AppSettings.Debug), debug);
public AppSettingsBuilder WithMaxRetries(int retries) => Set(nameof(AppSettings.MaxRetries), retries);
public override AppSettings Build()
{
var settings = new AppSettings();
ApplyTo(settings);
if (string.IsNullOrEmpty(settings.AppName))
throw new InvalidOperationException("AppName 不能为空");
return settings;
}
}
public class AppSettings
{
public string AppName { get; set; } = "";
public string Version { get; set; } = "1.0.0";
public bool Debug { get; set; }
public int MaxRetries { get; set; } = 3;
}
// 使用
var settings = new AppSettingsBuilder()
.WithAppName("MyApp")
.WithVersion("2.0.0")
.WithDebug(true)
.WithMaxRetries(5)
.Build();实战:线程安全的 Builder(通过不可变快照)
Builder 本身不是线程安全的,但在某些场景下需要并发构建。可以通过快照模式实现。
// 不可变快照 — 线程安全
public sealed record HttpRequestSnapshot(
string Url,
string Method,
IReadOnlyDictionary<string, string> Headers,
IReadOnlyDictionary<string, string> QueryParams,
string? Body,
TimeSpan Timeout,
int RetryCount,
bool EnableCache);
// 线程安全的 Builder — 每次修改返回新实例
public sealed class ThreadSafeRequestBuilder
{
private readonly string _url;
private readonly string _method;
private readonly ImmutableDictionary<string, string> _headers;
private readonly ImmutableDictionary<string, string> _queryParams;
private readonly string? _body;
private readonly TimeSpan _timeout;
private readonly int _retryCount;
private readonly bool _enableCache;
private ThreadSafeRequestBuilder(
string url, string method,
ImmutableDictionary<string, string> headers,
ImmutableDictionary<string, string> queryParams,
string? body, TimeSpan timeout, int retryCount, bool enableCache)
{
_url = url; _method = method; _headers = headers;
_queryParams = queryParams; _body = body; _timeout = timeout;
_retryCount = retryCount; _enableCache = enableCache;
}
public static ThreadSafeRequestBuilder Create(string url) =>
new(url, "GET", ImmutableDictionary<string, string>.Empty,
ImmutableDictionary<string, string>.Empty, null,
TimeSpan.FromSeconds(30), 0, false);
public ThreadSafeRequestBuilder WithMethod(string method) =>
new(_url, method, _headers, _queryParams, _body, _timeout, _retryCount, _enableCache);
public ThreadSafeRequestBuilder WithHeader(string key, string value) =>
new(_url, _method, _headers.Add(key, value), _queryParams, _body, _timeout, _retryCount, _enableCache);
public ThreadSafeRequestBuilder WithBody(string body) =>
new(_url, _method, _headers, _queryParams, body, _timeout, _retryCount, _enableCache);
public HttpRequestSnapshot Build()
{
if (string.IsNullOrWhiteSpace(_url))
throw new InvalidOperationException("URL 不能为空");
return new HttpRequestSnapshot(
_url, _method, _headers, _queryParams, _body, _timeout, _retryCount, _enableCache);
}
}建造者 vs 工厂方法 vs 抽象工厂
建造者 工厂方法 抽象工厂
+--------+ +--------+ +--------+
| 分步 | | 一个 | | 一族 |
| 构建 | | 对象 | | 对象 |
+--------+ +--------+ +--------+
| 复杂 | | 单一 | | 多个 |
| 对象 | | 创建 | | 相关 |
+--------+ +--------+ +--------+
| 可选 | | 子类 | | 产品 |
| 参数多 | | 决定 | | 一致 |
+--------+ +--------+ +--------+何时选择建造者而非构造函数
选择建造者模式还是直接使用构造函数,取决于参数的复杂度和使用场景。以下决策树可以帮助判断:
参数数量 <= 3 且都是必填?
└─ 是 → 使用构造函数或 record 主构造函数
└─ 否 → 有多个可选参数?
└─ 是 → 使用 Builder 模式
└─ 否 → 参数之间有约束关系?
└─ 是 → 使用 Builder(在 Build 中验证)
└─ 否 → 使用构造函数即可
对象需要多步构建才能到达有效状态?
└─ 是 → 使用 Builder(保护不变量)
└─ 否 → 直接构造即可
构建过程需要复用(相同的前几步,不同的后续步骤)?
└─ 是 → 使用 Builder + Director
└─ 否 → 使用简单 BuilderBuilder 的常见陷阱与规避方法
// 陷阱 1:Builder 复用导致状态污染
var builder = HttpRequestBuilder.Create().WithUrl("https://a.com");
var req1 = builder.WithMethod("GET").Build();
var req2 = builder.WithMethod("POST").Build(); // builder 的 _method 已被修改
// 规避:Build 后不要复用同一个 Builder,或在 Build 中重置状态
// 陷阱 2:忘记调用 Build() 导致使用未构建的 Builder
var builder = HttpRequestBuilder.Create().WithUrl("https://a.com");
// var config = builder; // 编译时不会报错,但类型不匹配
// 规避:让 Builder 和 Product 的类型完全不同,避免误用
// 陷阱 3:Builder 属性与 Product 属性不同步
// 新增 Product 属性后忘记在 Builder 中添加对应方法
// 规避:使用 Source Generator 自动生成 Builder 代码
// 陷阱 4:Build() 中验证不充分
public HttpRequestConfig Build()
{
// 不仅要检查必填项,还要检查逻辑约束
if (string.IsNullOrWhiteSpace(_url))
throw new InvalidOperationException("URL 不能为空");
if (_method == "GET" && _body != null)
throw new InvalidOperationException("GET 请求不能有 Body");
if (_retryCount < 0)
throw new InvalidOperationException("重试次数不能为负数");
// ... 返回产品
return new HttpRequestConfig { /* ... */ };
}最佳实践
- Build 方法返回不可变对象:使用
init属性或 record 类型,确保构建完成后对象不可变。 - 参数验证集中在 Build:将所有验证逻辑集中在 Build 方法中,避免部分构建状态。
- Builder 不持有产品引用:Build 返回新对象,Builder 可以复用。
- 考虑静态工厂方法:使用
Create()静态方法提供更清晰的 API 入口。 - 结合 record 使用:C# record 的
with表达式提供了非破坏性修改的简洁语法。
Builder 与 C# 新特性的结合
C# 12 主构造函数简化 Builder
// C# 12 的主构造函数可以进一步简化产品类型的定义
public class ServerConfig(
string Host,
int Port = 443,
bool UseSsl = true,
int MaxConnections = 100,
TimeSpan ConnectionTimeout = default,
string? CertificatePath = null)
{
// 便捷 Builder 入口
public static ServerConfigBuilder Builder(string host) => new(host);
public override string ToString() =>
$"{(UseSsl ? "https" : "http")}://{Host}:{Port} [MaxConn={MaxConnections}]";
}
public class ServerConfigBuilder
{
private string _host;
private int _port = 443;
private bool _useSsl = true;
private int _maxConnections = 100;
private TimeSpan _timeout = TimeSpan.FromSeconds(30);
private string? _certPath;
public ServerConfigBuilder(string host) => _host = host;
public ServerConfigBuilder WithPort(int port) { _port = port; return this; }
public ServerConfigBuilder WithSsl(bool useSsl) { _useSsl = useSsl; return this; }
public ServerConfigBuilder WithMaxConnections(int max) { _maxConnections = max; return this; }
public ServerConfigBuilder WithTimeout(TimeSpan timeout) { _timeout = timeout; return this; }
public ServerConfigBuilder WithCertificate(string path) { _certPath = path; return this; }
public ServerConfig Build()
{
if (_useSsl && string.IsNullOrEmpty(_certPath) && _port != 443)
throw new InvalidOperationException("SSL 需要指定证书路径");
return new ServerConfig(_host, _port, _useSsl, _maxConnections, _timeout, _certPath);
}
}
var server = ServerConfig.Builder("api.example.com")
.WithPort(8443)
.WithMaxConnections(500)
.WithTimeout(TimeSpan.FromSeconds(10))
.WithCertificate("/certs/server.pfx")
.Build();使用 required 属性替代 Builder 的部分场景
// C# 11 的 required 属性 + init 可以部分替代 Builder
public class CacheOptions
{
public required string CacheKey { get; init; }
public TimeSpan SlidingExpiration { get; init; } = TimeSpan.FromMinutes(20);
public TimeSpan? AbsoluteExpiration { get; init; }
public int MaxSize { get; init; } = 1024;
public bool Compress { get; init; }
}
// 使用对象初始化器 — 简洁但缺少验证逻辑
var options = new CacheOptions
{
CacheKey = "user:123",
SlidingExpiration = TimeSpan.FromMinutes(5),
Compress = true
};
// 对于复杂验证逻辑,Builder 仍然更合适
// 对于简单配置,required + init 已经足够优点
缺点
总结
建造者模式通过链式 Fluent API 分步构建复杂对象。Director 封装标准构建流程,Builder 负责具体组装。C# 中配合 record 类型可简化实现。核心价值是解决构造函数参数过多的问题,同时提供参数验证和不可变保证。建议在对象有 4 个以上参数(尤其是可选参数)时使用建造者模式。
建造者模式的本质价值在于:当你需要创建一个具有多个可选参数的复杂对象时,建造者模式提供了一种可读性高、类型安全、可验证的构建方式。通过链式调用,代码读起来就像是在描述对象的属性,而不是在填写一个长长的参数列表。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《建造者模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《建造者模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《建造者模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《建造者模式》最大的收益和代价分别是什么?
