工厂模式详解
大约 10 分钟约 3042 字
工厂模式详解
简介
工厂模式是创建型设计模式中最重要的一族,包括简单工厂、工厂方法和抽象工厂三种形式。它们的核心思想是将对象的创建过程封装起来,使客户端代码不需要知道具体创建了哪个类的实例。在 C# 中,工厂模式与依赖注入、泛型、反射等特性结合使用,可以构建灵活且可扩展的对象创建体系。
特点
简单工厂模式
简单工厂不属于 GoF 23 种模式,但它是理解工厂模式的基础。通过一个工厂类根据参数决定创建哪种产品。
// 产品接口
public interface IMessage
{
string Content { get; }
string Type { get; }
DateTime CreatedAt { get; }
}
// 具体产品
public class EmailMessage : IMessage
{
public string Content { get; set; }
public string Type => "Email";
public DateTime CreatedAt { get; } = DateTime.UtcNow;
public string To { get; set; }
public string Subject { get; set; }
}
public class SmsMessage : IMessage
{
public string Content { get; set; }
public string Type => "SMS";
public DateTime CreatedAt { get; } = DateTime.UtcNow;
public string PhoneNumber { get; set; }
}
public class PushMessage : IMessage
{
public string Content { get; set; }
public string Type => "Push";
public DateTime CreatedAt { get; } = DateTime.UtcNow;
public string DeviceToken { get; set; }
}
// 简单工厂
public static class MessageFactory
{
public static IMessage Create(string type, string content)
{
return type.ToLower() switch
{
"email" => new EmailMessage { Content = content },
"sms" => new SmsMessage { Content = content },
"push" => new PushMessage { Content = content },
_ => throw new ArgumentException($"不支持的消息类型: {type}")
};
}
}
// 使用
var email = MessageFactory.Create("email", "欢迎注册");
var sms = MessageFactory.Create("sms", "验证码: 123456");工厂方法模式
工厂方法定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
// 抽象产品
public interface IDocument
{
string Title { get; set; }
string Content { get; set; }
byte[] Export();
}
// 具体产品
public class PdfDocument : IDocument
{
public string Title { get; set; }
public string Content { get; set; }
public byte[] Export()
{
// PDF 导出逻辑
Console.WriteLine($"导出 PDF 文档: {Title}");
return System.Text.Encoding.UTF8.GetBytes($"[PDF]{Title}\n{Content}");
}
}
public class WordDocument : IDocument
{
public string Title { get; set; }
public string Content { get; set; }
public byte[] Export()
{
Console.WriteLine($"导出 Word 文档: {Title}");
return System.Text.Encoding.UTF8.GetBytes($"[DOCX]{Title}\n{Content}");
}
}
public class MarkdownDocument : IDocument
{
public string Title { get; set; }
public string Content { get; set; }
public byte[] Export()
{
Console.WriteLine($"导出 Markdown 文档: {Title}");
return System.Text.Encoding.UTF8.GetBytes($"# {Title}\n\n{Content}");
}
}
// 抽象工厂
public abstract class DocumentFactory
{
// 工厂方法
public abstract IDocument CreateDocument();
// 模板方法,使用工厂创建的产品
public IDocument BuildReport(string title, string content)
{
var doc = CreateDocument();
doc.Title = title;
doc.Content = content;
return doc;
}
}
// 具体工厂
public class PdfFactory : DocumentFactory
{
public override IDocument CreateDocument() => new PdfDocument();
}
public class WordFactory : DocumentFactory
{
public override IDocument CreateDocument() => new WordDocument();
}
public class MarkdownFactory : DocumentFactory
{
public override IDocument CreateDocument() => new MarkdownDocument();
}
// 使用
DocumentFactory factory = new PdfFactory();
var doc = factory.BuildReport("月度报告", "本月销售额增长了 20%...");
var bytes = doc.Export();工厂方法结合泛型
// 泛型工厂 - 更优雅的实现
public interface IFactory<T> where T : new()
{
T Create();
}
public class GenericFactory<T> : IFactory<T> where T : new()
{
private readonly Action<T>? _configure;
public GenericFactory() { }
public GenericFactory(Action<T> configure)
{
_configure = configure;
}
public T Create()
{
var instance = new T();
_configure?.Invoke(instance);
return instance;
}
}
// 使用泛型工厂
var pdfFactory = new GenericFactory<PdfDocument>(doc =>
{
doc.Title = "默认标题";
});
var document = pdfFactory.Create();抽象工厂模式
抽象工厂提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
// 跨平台 UI 组件的抽象工厂示例
// 抽象产品族
public interface IButton
{
void Render();
void OnClick(Action callback);
}
public interface ITextBox
{
void Render();
string Text { get; set; }
}
public interface IWindow
{
string Title { get; set; }
void Show();
void Close();
}
// Windows 实现
public class WindowsButton : IButton
{
private Action? _onClick;
public void Render() => Console.WriteLine("[Windows] 渲染标准按钮");
public void OnClick(Action callback) => _onClick = callback;
}
public class WindowsTextBox : ITextBox
{
public string Text { get; set; } = "";
public void Render() => Console.WriteLine("[Windows] 渲染文本框");
}
public class WindowsWindow : IWindow
{
public string Title { get; set; } = "Windows Window";
public void Show() => Console.WriteLine($"[Windows] 显示窗口: {Title}");
public void Close() => Console.WriteLine($"[Windows] 关闭窗口");
}
// Web 实现
public class WebButton : IButton
{
private Action? _onClick;
public void Render() => Console.WriteLine("<button class='btn'>点击</button>");
public void OnClick(Action callback) => _onClick = callback;
}
public class WebTextBox : ITextBox
{
public string Text { get; set; } = "";
public void Render() => Console.WriteLine("<input type='text' />");
}
public class WebWindow : IWindow
{
public string Title { get; set; } = "Web Window";
public void Show() => Console.WriteLine($"[Web] 打开弹窗: {Title}");
public void Close() => Console.WriteLine("[Web] 关闭弹窗");
}
// 抽象工厂
public interface IUIFactory
{
IButton CreateButton();
ITextBox CreateTextBox();
IWindow CreateWindow();
}
// 具体工厂
public class WindowsUIFactory : IUIFactory
{
public IButton CreateButton() => new WindowsButton();
public ITextBox CreateTextBox() => new WindowsTextBox();
public IWindow CreateWindow() => new WindowsWindow();
}
public class WebUIFactory : IUIFactory
{
public IButton CreateButton() => new WebButton();
public ITextBox CreateTextBox() => new WebTextBox();
public IWindow CreateWindow() => new WebWindow();
}
// 客户端使用
public class Application
{
private readonly IButton _button;
private readonly ITextBox _textBox;
private readonly IWindow _window;
public Application(IUIFactory factory)
{
_button = factory.CreateButton();
_textBox = factory.CreateTextBox();
_window = factory.CreateWindow();
}
public void Run()
{
_window.Title = "我的应用";
_window.Show();
_textBox.Text = "Hello World";
_textBox.Render();
_button.Render();
}
}
// 根据平台创建不同的工厂
IUIFactory factory = Environment.OSVersion.Platform == PlatformID.Win32NT
? new WindowsUIFactory()
: new WebUIFactory();
var app = new Application(factory);
app.Run();抽象工厂结合 DI 容器
// 在 ASP.NET Core 中注册抽象工厂
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IUIFactory, WebUIFactory>();
builder.Services.AddTransient<Application>();
// 也可以使用工厂委托注册
builder.Services.AddTransient<IDocument>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var format = config["Document:Format"] ?? "Pdf";
return format switch
{
"Pdf" => new PdfDocument(),
"Word" => new WordDocument(),
"Markdown" => new MarkdownDocument(),
_ => throw new InvalidOperationException($"不支持的文档格式: {format}")
};
});优点
工厂模式实战场景
支付渠道工厂
// 实际业务中的工厂模式 — 多渠道支付
// 支付产品接口
public interface IPaymentProvider
{
string ProviderName { get; }
Task<PaymentResult> ChargeAsync(PaymentRequest request);
Task<RefundResult> RefundAsync(RefundRequest request);
Task<PaymentStatus> QueryStatusAsync(string transactionId);
}
// 支付宝实现
public class AlipayProvider : IPaymentProvider
{
public string ProviderName => "Alipay";
public async Task<PaymentResult> ChargeAsync(PaymentRequest request)
{
// 支付宝 API 调用逻辑
return new PaymentResult { Success = true, TransactionId = Guid.NewGuid().ToString() };
}
public async Task<RefundResult> RefundAsync(RefundRequest request)
{
return new RefundResult { Success = true };
}
public async Task<PaymentStatus> QueryStatusAsync(string transactionId)
{
return PaymentStatus.Success;
}
}
// 微信支付实现
public class WeChatPayProvider : IPaymentProvider
{
public string ProviderName => "WeChatPay";
public async Task<PaymentResult> ChargeAsync(PaymentRequest request)
{
return new PaymentResult { Success = true, TransactionId = Guid.NewGuid().ToString() };
}
public async Task<RefundResult> RefundAsync(RefundRequest request)
{
return new RefundResult { Success = true };
}
public async Task<PaymentStatus> QueryStatusAsync(string transactionId)
{
return PaymentStatus.Success;
}
}
// 银联支付实现
public class UnionPayProvider : IPaymentProvider
{
public string ProviderName => "UnionPay";
public async Task<PaymentResult> ChargeAsync(PaymentRequest request)
{
return new PaymentResult { Success = true, TransactionId = Guid.NewGuid().ToString() };
}
public async Task<RefundResult> RefundAsync(RefundRequest request)
{
return new RefundResult { Success = true };
}
public async Task<PaymentStatus> QueryStatusAsync(string transactionId)
{
return PaymentStatus.Success;
}
}
// 支付工厂 — 使用 DI 容器
public interface IPaymentFactory
{
IPaymentProvider GetProvider(string providerName);
IPaymentProvider GetProvider(PaymentMethod method);
}
public class PaymentFactory : IPaymentFactory
{
private readonly Dictionary<string, IPaymentProvider> _providers;
public PaymentFactory(IEnumerable<IPaymentProvider> providers)
{
// 通过 DI 注入所有支付实现
_providers = providers.ToDictionary(p => p.ProviderName, StringComparer.OrdinalIgnoreCase);
}
public IPaymentProvider GetProvider(string providerName)
{
if (_providers.TryGetValue(providerName, out var provider))
return provider;
throw new ArgumentException($"不支持的支付渠道: {providerName}");
}
public IPaymentProvider GetProvider(PaymentMethod method)
{
return method switch
{
PaymentMethod.Alipay => _providers["Alipay"],
PaymentMethod.WeChatPay => _providers["WeChatPay"],
PaymentMethod.UnionPay => _providers["UnionPay"],
_ => throw new ArgumentException($"不支持的支付方式: {method}")
};
}
}
// DI 注册
builder.Services.AddSingleton<IPaymentFactory, PaymentFactory>();
builder.Services.AddScoped<IPaymentProvider, AlipayProvider>();
builder.Services.AddScoped<IPaymentProvider, WeChatPayProvider>();
builder.Services.AddScoped<IPaymentProvider, UnionPayProvider>();日志处理器工厂
// 多格式日志处理器的工厂模式
public interface ILogHandler
{
string Format { get; }
Task HandleAsync(LogEntry entry);
}
public class JsonLogHandler : ILogHandler
{
public string Format => "json";
public async Task HandleAsync(LogEntry entry)
{
var json = JsonSerializer.Serialize(entry);
await File.AppendAllTextAsync("logs.json", json + Environment.NewLine);
}
}
public class CsvLogHandler : ILogHandler
{
public string Format => "csv";
public async Task HandleAsync(LogEntry entry)
{
var line = $"{entry.Timestamp:O},{entry.Level},{entry.Message},{entry.Source}";
await File.AppendAllTextAsync("logs.csv", line + Environment.NewLine);
}
}
public class ElasticsearchLogHandler : ILogHandler
{
public string Format => "elasticsearch";
public async Task HandleAsync(LogEntry entry)
{
// 推送到 Elasticsearch
}
}
// 工厂 — 根据配置选择处理器
public class LogHandlerFactory
{
private readonly Dictionary<string, ILogHandler> _handlers;
private readonly IConfiguration _configuration;
public LogHandlerFactory(IEnumerable<ILogHandler> handlers, IConfiguration configuration)
{
_handlers = handlers.ToDictionary(h => h.Format, StringComparer.OrdinalIgnoreCase);
_configuration = configuration;
}
public ILogHandler GetHandler()
{
var format = _configuration["Logging:Format"] ?? "json";
return _handlers.TryGetValue(format, out var handler)
? handler
: throw new InvalidOperationException($"不支持的日志格式: {format}");
}
public IEnumerable<ILogHandler> GetHandlers(params string[] formats)
{
foreach (var format in formats)
{
if (_handlers.TryGetValue(format, out var handler))
yield return handler;
}
}
}工厂模式与设计原则
三种工厂模式对比
简单工厂:
- 一个工厂类,通过 switch/if 决定创建哪种产品
- 违反开闭原则,新增产品需要修改工厂类
- 适合产品种类少且稳定的场景
- 示例:配置驱动的日志格式选择
工厂方法:
- 每种产品对应一个工厂类
- 符合开闭原则,新增产品只需新增工厂类
- 适合单一产品体系,需要灵活扩展的场景
- 示例:文档导出(PDF/Word/Markdown 各有独立工厂)
抽象工厂:
- 每个产品族对应一个工厂类
- 保证同一族的产品一致性
- 新增产品族容易,新增产品类型困难
- 适合多产品族需要保持一致性的场景
- 示例:跨平台 UI 组件(Windows/Web/Mobile 各一套组件)
选择建议:
1. 如果只有一种产品,用简单工厂或工厂方法
2. 如果有多种相关产品需要保持一致性,用抽象工厂
3. 如果产品类型经常变化,优先用工厂方法
4. 实际开发中,大多数场景用 DI 容器 + 简单工厂就够了缺点
总结
工厂模式家族的三种形式各有适用场景:简单工厂适合产品类型少且不常变化的场景;工厂方法适合单一产品体系且需要灵活扩展的场景;抽象工厂适合多个相关产品族需要保持一致性的场景。在 .NET 实际开发中,工厂模式经常与依赖注入容器结合使用,通过 IServiceProvider 和工厂委托实现运行时的对象创建,既保持了灵活性又简化了代码结构。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来“高级”而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《工厂模式详解》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕“为了模式而模式”,尤其是在简单业务里。
复盘问题
- 如果把《工厂模式详解》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《工厂模式详解》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《工厂模式详解》最大的收益和代价分别是什么?
