-
接口与抽象类——用C#开发支付方式统一接口设计
第一部分:C#基础入门
实例3:接口与抽象类——支付方式统一接口设计
实例介绍
电商系统中,支付方式五花八门(微信、支付宝、银联),但核心流程高度一致:创建订单→发起支付→查询状态。如果每个支付方式都单独写一套逻辑,不仅重复代码多,新增方式时还得改原有代码(违反开放封闭原则)。
这节我们用接口定义支付契约、抽象类封装公共逻辑,让不同支付方式既遵vb.net教程C#教程python教程SQL教程access 2010教程循统一标准,又保留各自实现细节。通过这个案例,你能搞懂“什么时候用接口、什么时候用抽象类”,以及如何用它们写出灵活、易扩展的代码。
需求分析
1.统一支付接口:定义IPayable接口,包含所有支付方式必须实现的核心方法(创建订单、支付、查询状态);
2.抽象类封装公共逻辑:BasePayment抽象类封装日志、参数校验等所有支付方式都需要的通用操作;
3.具体支付方式实现:微信(WeChatPay)、支付宝(Alipay)、银联(UnionPay)继承抽象类,实现接口方法;
4.支付管理器:用多态统一处理支付请求,不管是哪种方式,都能通过接口调用;
5.易扩展性:新增支付方式(比如PayPal)时,无需修改现有代码,只需新增子类即可。
代码实现
csharp
using System;
using System.Globalization;
namespace PaymentSystemDemo
{
// 1. 支付接口:定义所有支付方式必须遵守的契约
public interface IPayable
{
// 创建支付订单(返回第三方支付订单号)
string CreateOrder(string merchantOrderId, decimal amount);
// 发起支付(返回支付是否成功)
bool Pay(string paymentOrderId);
// 查询支付状态(返回状态描述:成功/失败/处理中)
string QueryStatus(string paymentOrderId);
}
// 2. 抽象支付类:封装公共逻辑(日志、参数校验)
public abstract class BasePayment : IPayable
{
// 支付渠道名称(子类必须赋值)
protected string _channelName;
// 公共日志方法(子类可直接调用,外部不可见)
protected void Log(string message)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{_channelName}] {message}");
}
// 公共参数校验方法(子类创建订单时调用)
protected bool ValidateParams(string merchantOrderId, decimal amount)
{
if (string.IsNullOrWhiteSpace(merchantOrderId))
{
Log("商户订单ID不能为空");
return false;
}
if (amount <= 0)
{
Log($"金额{amount}不合法,必须大于0");
return false;
}
return true;
}
// 接口方法的抽象实现(子类必须重写)
public abstract string CreateOrder(string merchantOrderId, decimal amount);
public abstract bool Pay(string paymentOrderId);
public abstract string QueryStatus(string paymentOrderId);
}
// 3. 微信支付实现
public class WeChatPay : BasePayment
{
public WeChatPay()
{
_channelName = "微信支付";
}
public override string CreateOrder(string merchantOrderId, decimal amount)
{
if (!ValidateParams(merchantOrderId, amount)) return null;
Log($"创建订单:商户ID={merchantOrderId},金额={amount:C}");
// 模拟微信支付订单号生成(实际调用微信SDK)
return $"WX_{merchantOrderId}_{Guid.NewGuid().ToString("N").Substring(0, 8)}";
}
public override bool Pay(string paymentOrderId)
{
Log($"发起支付:订单ID={paymentOrderId}");
// 模拟微信支付成功(实际调用微信支付接口)
return true;
}
public override string QueryStatus(string paymentOrderId)
{
Log($"查询状态:订单ID={paymentOrderId}");
// 模拟微信支付状态查询
return "支付成功";
}
}
// 4. 支付宝支付实现
public class Alipay : BasePayment
{
public Alipay()
{
_channelName = "支付宝";
}
public override string CreateOrder(string merchantOrderId, decimal amount)
{
if (!ValidateParams(merchantOrderId, amount)) return null;
Log($"创建订单:商户ID={merchantOrderId},金额={amount:C}");
// 模拟支付宝订单号生成
return $"ALI_{merchantOrderId}_{DateTime.Now.Ticks.ToString().Substring(0, 10)}";
}
public override bool Pay(string paymentOrderId)
{
Log($"发起支付:订单ID={paymentOrderId}");
// 模拟支付宝支付成功
return true;
}
public override string QueryStatus(string paymentOrderId)
{
Log($"查询状态:订单ID={paymentOrderId}");
return "支付成功";
}
}
// 5. 银联支付实现
public class UnionPay : BasePayment
{
public UnionPay()
{
_channelName = "银联支付";
}
public override string CreateOrder(string merchantOrderId, decimal amount)
{
if (!ValidateParams(merchantOrderId, amount)) return null;
Log($"创建订单:商户ID={merchantOrderId},金额={amount:C}");
// 模拟银联订单号生成
return $"UNION_{merchantOrderId}_{new Random().Next(100000, 999999)}";
}
public override bool Pay(string paymentOrderId)
{
Log($"发起支付:订单ID={paymentOrderId}");
// 模拟银联支付成功
return true;
}
public override string QueryStatus(string paymentOrderId)
{
Log($"查询状态:订单ID={paymentOrderId}");
return "支付成功";
}
}
// 6. 支付管理器:统一处理支付请求(多态核心应用)
public static class PaymentManager
{
public static void ProcessPayment(IPayable payment, string merchantOrderId, decimal amount)
{
Console.WriteLine("
===== 开始支付流程 =====");
// 1. 创建支付订单
var paymentOrderId = payment.CreateOrder(merchantOrderId, amount);
if (string.IsNullOrWhiteSpace(paymentOrderId))
{
Console.WriteLine("===== 支付失败:订单创建失败 =====");
return;
}
// 2. 发起支付
bool paySuccess = payment.Pay(paymentOrderId);
if (!paySuccess)
{
Console.WriteLine("===== 支付失败:支付请求被拒绝 =====");
return;
}
// 3. 查询支付状态
string status = payment.QueryStatus(paymentOrderId);
Console.WriteLine($"===== 支付完成:状态={status} =====");
}
}
// 7. 主程序:模拟电商支付场景
class Program
{
static void Main(string[] args)
{
// 模拟商户订单信息
string merchantOrderId = "M202405200001";
decimal amount = 199.9m;
// 微信支付测试
Console.WriteLine("=== 微信支付测试 ===");
PaymentManager.ProcessPayment(new WeChatPay(), merchantOrderId, amount);
// 支付宝支付测试
Console.WriteLine("
=== 支付宝支付测试 ===");
PaymentManager.ProcessPayment(new Alipay(), merchantOrderId, amount);
// 银联支付测试
Console.WriteLine("
=== 银联支付测试 ===");
PaymentManager.ProcessPayment(new UnionPay(), merchantOrderId, amount);
}
}
}
逐行讲解
-
支付接口IPayable
csharp
public interface IPayable
{
string CreateOrder(string merchantOrderId, decimal amount);
bool Pay(string paymentOrderId);
string QueryStatus(string paymentOrderId);
}
接口的作用:定义“支付”这个行为的契约——不管哪种支付方式,都必须实现这三个方法。这就像一份协议,所有支付方式都得遵守,保证了统一的调用方式。
为什么用接口:支付方式是不同的类(微信、支付宝),但它们的核心行为一致,接口让这些类“看起来一样”,方便统一管理。
2. 抽象类BasePayment
csharp
public abstract class BasePayment : IPayable
{
protected string _channelName;
protected void Log(string message) { ... }
protected bool ValidateParams(...) { ... }
public abstract string CreateOrder(...) { ... }
}
抽象类的定位:封装公共逻辑,避免子类重复写代码。比如日志(每个支付方式都要打日志)、参数校验(每个支付方式都要校验订单ID和金额)。
protected修饰符:Log和ValidateParams方法用protected,意味着子类可以直接调用,但外部代码不能访问——既复用了代码,又保证了封装性。
抽象方法:CreateOrder等方法用abstract,说明子类必须重写这些方法——公共逻辑在父类,具体实现留给子类。
3. 微信支付子类WeChatPay
csharp
public class WeChatPay : BasePayment
{
public WeChatPay() { _channelName = "微信支付"; }
public override string CreateOrder(...)
{
if (!ValidateParams(...)) return null;
Log(...);
return $"WX_{merchantOrderId}_{...}";
}
}
继承抽象类:WeChatPay继承BasePayment,自动获得Log和ValidateParams方法,不用自己写。
重写抽象方法:CreateOrder方法里,先调用父类的ValidateParams校验参数,再打日志,最后生成微信支付的订单号——这就是“公共逻辑+具体实现”的完美结合。
构造函数赋值:_channelName是父类的protected字段,子类构造函数里赋值为“微信支付”,这样日志里就能显示支付渠道名称。
4. 支付管理器PaymentManager
csharp
public static void ProcessPayment(IPayable payment, ...)
{
var orderId = payment.CreateOrder(...);
bool success = payment.Pay(orderId);
string status = payment.QueryStatus(orderId);
}
多态的核心:ProcessPayment方法的参数是IPayable接口,不管传入的是WeChatPay、Alipay还是UnionPay,都能调用CreateOrder等方法——因为它们都实现了IPayable接口。
开放封闭原则:如果要新增PayPal支付,只需写一个PayPalPay类继承BasePayment并实现IPayable,不用修改ProcessPayment方法——这就是“对扩展开放,对修改关闭”。
5. 主程序测试
csharp
PaymentManager.ProcessPayment(new WeChatPay(), merchantOrderId, amount);
PaymentManager.ProcessPayment(new Alipay(), merchantOrderId, amount);
统一调用:不管是微信还是支付宝,调用方式完全一样——PaymentManager.ProcessPayment一句话搞定。新增支付方式时,只需加一行类似的代码,不用改任何已有逻辑。
基础知识拓展
- 接口vs抽象类:什么时候用哪个?
| 场景 | 用接口 | 用抽象类 |
|---|---|---|
| 定义行为契约(比如支付、打印) | ✅ | ❌ |
| 封装公共逻辑(比如日志、校验) | ❌ | ✅ |
| 多继承需求(一个类需要多个行为) | ✅(可实现多个接口) | ❌(只能继承一个抽象类) |
| 类之间无层级关系但有共同行为 | ✅ | ❌ |
| 类之间有层级关系且有公共逻辑 | ❌ | ✅ |
支付场景的选择逻辑:
支付方式的核心行为(创建订单、支付)用接口定义,保证统一调用;
公共逻辑(日志、校验)用抽象类封装,避免重复代码;
子类继承抽象类并实现接口,既复用了公共逻辑,又遵守了行为契约。
2. 接口的默认方法(C#8+)
C#8开始,接口可以有默认方法——当新增接口方法时,子类不用强制重写。比如给IPayable加一个Refund方法:
csharp
public interface IPayable
{
// 原有方法...
default bool Refund(string orderId)
{
Console.WriteLine("默认退款逻辑");
return true;
}
}
好处:新增功能时不破坏现有代码——子类可以选择重写Refund方法,也可以直接用默认实现。
3. 依赖倒置原则(DIP)
这个案例完美体现了依赖倒置原则:
高层模块(PaymentManager)不依赖低层模块(WeChatPay、Alipay),而是依赖抽象(IPayable);
低层模块依赖抽象:WeChatPay等子类实现IPayable接口。
这样,高层模块和低层模块都依赖抽象,新增低层模块(比如PayPalPay)时,高层模块不用改——这就是代码灵活的关键。
扩展思考
1.新增支付方式:写一个PayPalPay类,继承BasePayment并实现IPayable,看看是否需要修改PaymentManager?
2.支付异常处理:在子类的Pay方法里添加try-catch,处理第三方支付SDK的异常,并返回false;
3.依赖注入:用依赖注入框架(比如Autofac)注册IPayable的实现,替换主程序里的new WeChatPay(),实现解耦;
4.异步支付:把CreateOrder、Pay方法改成异步(async/await),适应实际支付场景中的异步接口;
5.支付回调:添加回调方法,处理第三方支付的异步通知(比如微信支付的回调接口)。
总结
接口和抽象类是C#面向对象编程的“黄金搭档”:
接口定义行为契约,保证统一调用;
抽象类封装公共逻辑,避免重复代码;
两者结合,能写出高复用、高扩展、易维护的代码——新增功能不用改旧代码,只需加新类。
通过支付系统这个案例,你应该能明白:接口是“做什么”,抽象类是“怎么做(公共部分)”,子类是“具体怎么做(个性化部分)”。掌握了这个逻辑,你就能应对大部分复杂的业务场景了!
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49404.html










