云计算百科
云计算领域专业知识百科平台

工厂方法模式实战:让支付渠道接入告别“改一处、崩一片“

工厂方法模式实战:让支付渠道接入告别"改一处、崩一片"

一句话定位:定义一个创建对象的接口,让子类决定实例化哪一个类,把"对象的创建"从"对象的使用"中解耦出来。

一、实战场景

电商系统里,支付是最核心、也最常变的一条链路。今天支持支付宝和微信,下个月要接入云闪付,过阵子可能还要接国际信用卡。

如果按最直觉的写法,业务层会写成这样:

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); // 统一调用,不关心实现细节
}
}

四、应用要点与注意事项

  • 开闭原则落地:新增支付渠道时,只需要"加一个实现类 + 在 DI 里注册 + 工厂 switch 加一行",业务代码零改动。这就是典型的"对扩展开放、对修改关闭"。
  • 配合 DI 容器使用:工厂内部不 new 渠道对象,而是从 IServiceProvider 解析。这样渠道依赖的 HttpClient、配置、日志都能由容器统一管理,也方便单元测试时替换。
  • 工厂方法 vs 简单工厂:简单工厂把 switch 写在静态方法里,每次新增都要改工厂本身;工厂方法模式将创建职责下沉到具体工厂子类。项目里如果渠道种类不多且稳定,简单工厂也够用;追求极致解耦才需要把工厂再抽象一层。
  • 错误处理边界:Create 抛 NotSupportedException 应发生在请求入口层,业务层拿到 IPaymentChannel 后默认它一定可用,避免业务代码里到处兜底。
  • 不要滥用:只有一个产品实现时,工厂纯属多余。等出现"第二个实现"或"创建逻辑开始重复"的信号,再引入工厂。
  • 五、小结

    工厂方法模式解决的核心痛点是:让"创建对象"这件事不污染"使用对象"的业务代码。在支付、短信、存储、消息队列这类"多供应商、高频率扩展"的场景里,它是保持系统可维护性的基本功。配合 .NET 的 DI 容器,这套写法几乎零成本,却能让后续每一次渠道接入都变成流水线操作。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 工厂方法模式实战:让支付渠道接入告别“改一处、崩一片“
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!