為什么你應該避免使用“switch語句”,改用多態(tài)
注:这里的标题更口语化,直接点明了文章的核心观点,建议在正式场合使用时可以稍作修改,以确保准确传达原意。
我收到了一条很棒的评论,最近我在PR中收到。
这个 switch 表达式不太好;建议使用多态来解决问题。
这让我开始琢磨——为什么“switch 语句模式”在现代软件设计中不被鼓励?如果你一直在编写 C#(或其他面向对象的语言),你可能也注意到了类似的说法。让我们来看看为什么这被认为是一种糟糕的方式,以及如何更好地重构它。
关于“switch语句多态性”的问题基于类型或枚举值的 switch 语句是你的代码可能违反了开闭原则(OCP)这一 SOLID 设计原则中的一个核心原则的经典标志。OCP 原则指出,你的代码应该易于扩展,但不易于修改现有代码。随着新情况被添加,switch 语句不断增长,迫使你修改现有代码,使其变得脆弱且更难维护,增加了代码的复杂性。
这里是一个关于 switch 语句多态性的示例。
设想一个支付处理的基本系统,比如:
public class PaymentProcessor
{
public void ProcessPayment(PaymentType paymentType)
{
switch (paymentType)
{
case PaymentType.CreditCard:
ProcessCreditCardPayment();
break;
case PaymentType.PayPal:
ProcessPayPalPayment();
break;
case PaymentType.Bitcoin:
ProcessBitcoinPayment();
break;
default:
throw new ArgumentException("不支持的付款方式");
}
}
}
乍一看,这似乎没什么问题,但实际上,每次新增加一种支付方式时,我们就得修改这个switch
语句。这明显违背了开闭原则(OCP),增加不必要的维护工作,并提高了引入错误的风险。
与其使用switch
,我们应重构设计,利用多态性同时确保在运行时动态确定正确的实现。具体做法如下:
- 定义一个处理支付的抽象基类或接口,以便其他类继承或实现。
- 为每种支付类型实现具体的类,这样可以针对每种支付方式提供特定的功能。
- 使用 依赖注入 (DI) 和 工厂模式 来实例化正确的类,确保代码的灵活性和可扩展性。
public interface IPaymentProcessor
{
bool 支持支付方式(PaymentType 支付方式);
void 处理支付();
}
public class 信用卡支付处理器 : IPaymentProcessor
{
public bool 支持支付方式(PaymentType 支付方式) => 支付方式 == PaymentType.CreditCard;
public void 处理支付()
{
// 处理信用卡支付交易
}
}
public class PayPal支付处理器 : IPaymentProcessor
{
public bool 支持支付方式(PaymentType 支付方式) => 支付方式 == PaymentType.PayPal;
public void 处理支付()
{
// 处理PayPal支付交易
}
}
public class 比特币支付处理器 : IPaymentProcessor
{
public bool 支持支付方式(PaymentType 支付方式) => 支付方式 == PaymentType.Bitcoin;
public void 处理支付()
{
// 处理比特币支付交易
}
}
运时确定正确的处理器
缺失的部分在于如何在运行时正确选择实现方式。这时就轮到工厂上场了。
public class PaymentProcessorFactory
{
private readonly IEnumerable<IPaymentProcessor> _paymentProcessors;
public PaymentProcessorFactory(IEnumerable<IPaymentProcessor> paymentProcessors)
{
_paymentProcessors = paymentProcessors;
}
public IPaymentProcessor GetProcessor(PaymentType paymentType)
{
return _paymentProcessors.SingleOrDefault(p => p.支持支付方式(paymentType))
?? throw new InvalidOperationException("未找到处理此支付类型的处理器");
}
}
在 PaymentService
中使用它
或:
我们可以在PaymentService
中使用它
public class 支付服务类
{
private readonly 支付处理器工厂 _支付处理器工厂;
public 支付服务类(支付处理器工厂 支付处理器工厂)
{
_支付处理器工厂 = 支付处理器工厂;
}
/// <summary>
/// 根据支付类型处理支付。
/// </summary>
/// <param name="支付类型">支付类型</param>
public void 处理支付(支付类型 支付类型)
{
var 处理器 = _支付处理器工厂.GetProcessor(支付类型);
处理器.ProcessPayment();
}
}
这种方法为什么更好
- 可扩展性:可以在不修改现有代码的情况下添加新的行为。
- 可维护性:代码遵循单一职责原则(SRP),通过分离关注点来实现。
- 可测试性:单元测试更简单,因为每个类都可以独立进行测试。
- 运行时灵活性:正确的处理器被动态解析,使其更具灵活性。
开关语句可能起初看起来很方便,但它们常常会导致长期维护的难题。采用多态性,我们可以创建一个更干净、更可扩展的设计,符合SOLID原则。虽然这种方法将开关语句从核心逻辑中移除,但它并未消除运行时决策的必要性——它只是将这一职责委托给利用依赖注入的工厂。这避免了紧耦合,使得代码库能够保持开放性,便于未来的扩展,而无需修改现有逻辑。
如果你收到PR中的建议,用多态来替换 switch 语句,不要把它当作批评性建议——把它当作提升代码库质量的机会。未来的你(以及你的队友们)会感激你的。
最后的想法软件设计不仅在于解决当前问题,还在于预见变化。通过避免使用“switch语句多态性”并拥抱多态性,你不仅是在写代码,更是在构建一个能够应对未来各种挑战的系统,准备好迎接未来的任何挑战。
下次当你想要使用 switch 语句时,先问问自己,是否有更优雅、更易于扩展的方式来解决这个问题?答案很可能就是肯定的。
参考资料- .NET 文档:多态
- C# 中的 SOLID 原则
- 开闭原则.
- Toby🙏
共同學習,寫下你的評論
評論加載中...
作者其他優(yōu)質文章