工厂方法模式实战:让支付渠道接入告别"改一处、崩一片"
一句话定位:定义一个创建对象的接口,让子类决定实例化哪一个类,把"对象的创建"从"对象的使用"中解耦出来。
一、实战场景
电商系统里,支付是最核心、也最常变的一条链路。今天支持支付宝和微信,下个月要接入云闪付,过阵子可能还要接国际信用卡。
如果按最直觉的写法,业务层会写成这样:
public async Task<PaymentResult> PayAsync(PaymentMethod method, PaymentRequest request)
{
if (method == PaymentMethod.Alipay)
{
// 调用支付宝 SDK 的逻辑,几十行……
}
else if (method == PaymentMethod.WeChat)
{
// 调用微信 SDK 的逻辑,又是几十行……
}
// 每加一个渠道,这里就多一个分支
}
一旦渠道超过两三个,这个方法的体积、圈复杂度会迅速失控:新增一个渠道要动业务代码,渠道本身的逻辑和业务逻辑又牢牢耦合在一起——这正是工厂方法模式要解决的问题。
二、设计思路
把"支付渠道"抽象成一个统一接口(抽象产品),每个具体渠道是一个实现类(具体产品);再引入一个工厂,负责根据参数创建对应的渠道对象。业务调用方只依赖抽象接口和工厂,完全不知道也不关心背后是哪个 SDK。
┌───────────────┐ 创建 ┌───────────────────┐
│ 业务调用方 │─────────▶│ PaymentChannelFactory│
└───────────────┘ └─────────┬─────────┘
│ 创建
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ AlipayChannel │ │ WechatChannel │ │ UnionPayChannel│
└──────────────┘ └──────────────┘ └──────────────┘
全部实现 IPaymentChannel 接口
三、代码实现(.NET 10 / C# 14)
3.1 抽象产品与请求模型
namespace Payments.Channels;
public enum PaymentMethod
{
Alipay,
WeChat,
UnionPay
}
public sealed record PaymentRequest(
string OrderId,
decimal Amount,
string Subject);
public sealed record PaymentResult(
bool Success,
string Channel,
string TransactionId,
string Message);
3.2 抽象产品接口
namespace Payments.Channels;
public interface IPaymentChannel
{
string ChannelName { get; }
Task<PaymentResult> PayAsync(PaymentRequest request, CancellationToken ct = default);
Task<PaymentResult> RefundAsync(string transactionId, decimal amount, CancellationToken ct = default);
}
3.3 具体产品:三个渠道实现
namespace Payments.Channels;
public sealed class AlipayChannel : IPaymentChannel
{
private readonly IHttpClientFactory _httpClientFactory;
// .NET 10 主构造函数,依赖注入更简洁
public AlipayChannel(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory;
public string ChannelName => "Alipay";
public async Task<PaymentResult> PayAsync(PaymentRequest request, CancellationToken ct = default)
{
var client = _httpClientFactory.CreateClient("alipay");
// 真实项目:组装支付宝开放平台请求、验签、下单
var response = await client.PostAsJsonAsync("/gateway.do", new
{
method = "alipay.trade.page.pay",
out_trade_no = request.OrderId,
total_amount = request.Amount
}, ct);
return response.IsSuccessStatusCode
? new PaymentResult(true, ChannelName, $"ALI-{request.OrderId}", "支付成功")
: new PaymentResult(false, ChannelName, "", "支付宝下单失败");
}
public Task<PaymentResult> RefundAsync(string transactionId, decimal amount, CancellationToken ct = default)
{
// 调用退款接口……
return Task.FromResult(new PaymentResult(true, ChannelName, transactionId, "退款成功"));
}
}
namespace Payments.Channels;
public sealed class WechatChannel : IPaymentChannel
{
private readonly IHttpClientFactory _httpClientFactory;
public WechatChannel(IHttpClientFactory httpClientFactory) => _httpClientFactory = httpClientFactory;
public string ChannelName => "WeChat";
public async Task<PaymentResult> PayAsync(PaymentRequest request, CancellationToken ct = default)
{
var client = _httpClientFactory.CreateClient("wechat");
// 真实项目:微信支付 v3 接口,需携带商户证书
var response = await client.PostAsJsonAsync("/v3/pay/transactions/native", new
{
out_trade_no = request.OrderId,
amount = new { total = (int)(request.Amount * 100), currency = "CNY" }
}, ct);
return response.IsSuccessStatusCode
? new PaymentResult(true, ChannelName, $"WX-{request.OrderId}", "支付成功")
: new PaymentResult(false, ChannelName, "", "微信下单失败");
}
public Task<PaymentResult> RefundAsync(string transactionId, decimal amount, CancellationToken ct = default)
=> Task.FromResult(new PaymentResult(true, ChannelName, transactionId, "退款成功"));
}
namespace Payments.Channels;
public sealed class UnionPayChannel : IPaymentChannel
{
public string ChannelName => "UnionPay";
public Task<PaymentResult> PayAsync(PaymentRequest request, CancellationToken ct = default)
{
// 云闪付/银联商务渠道逻辑……
return Task.FromResult(new PaymentResult(true, ChannelName, $"UP-{request.OrderId}", "支付成功"));
}
public Task<PaymentResult> RefundAsync(string transactionId, decimal amount, CancellationToken ct = default)
=> Task.FromResult(new PaymentResult(true, ChannelName, transactionId, "退款成功"));
}
3.4 工厂:创建逻辑的唯一入口
namespace Payments.Channels;
public interface IPaymentChannelFactory
{
IPaymentChannel Create(PaymentMethod method);
}
public sealed class PaymentChannelFactory(IServiceProvider services) : IPaymentChannelFactory
{
// 结合 DI 容器:新增渠道 = 新增实现类 + 注册服务,工厂本身无需再改
public IPaymentChannel Create(PaymentMethod method) => method switch
{
PaymentMethod.Alipay => services.GetRequiredService<AlipayChannel>(),
PaymentMethod.WeChat => services.GetRequiredService<WechatChannel>(),
PaymentMethod.UnionPay => services.GetRequiredService<UnionPayChannel>(),
_ => throw new NotSupportedException($"不支持的支付方式: {method}")
};
}
3.5 组装与使用
// Program.cs(.NET 10 顶层语句)
using Payments.Channels;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
builder.Services.AddSingleton<IPaymentChannelFactory, PaymentChannelFactory>();
builder.Services.AddSingleton<AlipayChannel>();
builder.Services.AddSingleton<WechatChannel>();
builder.Services.AddSingleton<UnionPayChannel>();
builder.Services.AddScoped<OrderService>();
var app = builder.Build();
app.MapPost("/orders/{orderId}/pay", async (string orderId, PaymentMethod method, OrderService orders) =>
{
var request = new PaymentRequest(orderId, amount: 199.00m, subject: "年度会员");
var result = await orders.PayAsync(method, request);
return Results.Ok(result);
});
app.Run();
// OrderService 中只依赖抽象接口和工厂
public sealed class OrderService(IPaymentChannelFactory channelFactory)
{
public async Task<PaymentResult> PayAsync(PaymentMethod method, PaymentRequest request)
{
var channel = channelFactory.Create(method); // 拿到渠道对象
return await channel.PayAsync(request); // 统一调用,不关心实现细节
}
}
四、应用要点与注意事项
五、小结
工厂方法模式解决的核心痛点是:让"创建对象"这件事不污染"使用对象"的业务代码。在支付、短信、存储、消息队列这类"多供应商、高频率扩展"的场景里,它是保持系统可维护性的基本功。配合 .NET 的 DI 容器,这套写法几乎零成本,却能让后续每一次渠道接入都变成流水线操作。
网硕互联帮助中心



评论前必须登录!
注册