C# 中的 Solid 原则

2025 年 4 月 25 日 | 阅读 9 分钟

SOLID 原则 是一组由五个设计原则组成的集合,这些原则有助于开发人员创建灵活、可扩展且易于维护的软件。为了解决面向对象软件设计中常见的问 题,Robert C. Martin 于 20 世纪 90 年代提出了这些思想。

开发人员可以通过遵循 SOLID 原则来确保他们的代码是可重用的、模块化的,并且符合最佳实践。借助这些原则,紧密耦合、僵化和单体代码可以转化为一个结构良好的系统,其中包含易于扩展、定义明确的组件。

SOLID 这个术语是一个代表五个核心原则的首字母缩略词

Solid Principles in C#
  • S:单一职责原则 (SRP)
  • O:开闭原则 (OCP)
  • L:里氏替换原则 (LSP)
  • I:接口隔离原则 (ISP)
  • D:依赖倒置原则 (DIP)

1. 单一职责原则 (SRP)

根据单一职责原则 (SRP),每个 软件 模块都应该只有一个引起其变化的原因。这意味着每个类或类似结构都应该专注于一个目标,而不是处理多个问题。一个类不应该像瑞士军刀一样,改变一个功能会影响其他功能。

SRP 表明,一个类的所有成员都应为单一职责做出贡献,而不是一个类应该只有一个函数或属性。开发人员可以通过遵循 SRP 来实现更好的模块化、可维护性和可测试性。

Solid Principles in C#

它有助于在应用程序设计过程中识别定义明确的类。但是,只有在理解了整个系统的架构后,才能正确地分离关注点。

C# 单一职责原则 (SRP) 示例

让我们看一个示例来演示 C# 中的单一职责原则 (SRP)。

示例

编译并运行

输出

 
Employee: Alice, Salary: 5000
Bonus: 500
Employee: Bob, Salary: 6000
Bonus: 600   

说明

在此示例中,我们演示了正确和错误地应用单一职责原则 (SRP)。由于 Employee 类同时管理员工信息和薪酬计算,它首先违反了 SRP,使其更难维护和更改。

在修改后的版本中,职责被分成两个不同的类:SalaryCalculator 函数负责薪酬相关的计算,EmployeeDetails 函数存储员工信息。通过确保一个职责的更改不会影响其他职责,代码变得更加模块化和易于管理。

2. 开闭原则 (OCP)

SOLID 原则之一,支持可扩展和稳定的软件设计,开闭原则 (OCP) 是 面向对象编程 (OOP) 的基础。类、模块和函数是软件实体的最佳示例,它们必须对扩展开放,但对修改关闭。

Solid Principles in C#

它有助于防止意外的副作用,并降低在进行修改时引入错误的风险。

C# 开闭原则 (OCP) 示例

让我们看一个示例来演示 C# 中的开闭原则 (OCP)。

示例

编译并运行

输出

 
Bad OCP: 20
Good OCP: 20   

说明

在此示例中,我们同时演示了开闭原则 (OCP) 的正确应用和违规。DiscountCalculatorBad 函数破坏了不良示例中的 OCP,因为它需要在每次添加新的客户类型时修改类,这使得代码更难维护。

另一方面,良好的示例使用多态性来遵守 OCP,具有多种折扣策略(RegularCustomerDiscount、PremiumCustomerDiscount 和 VIPCustomerDiscount),它们继承自 DiscountStrategy 抽象类。

通过接受 DiscountStrategy 对象,DiscountCalculatorGood 类使得在不修改现有代码的情况下引入新的折扣类型成为可能。

3. 里氏替换原则 (LSP)

里氏替换原则 (LSP) 由 Barbara Liskov 于 1987 年首次提出,是 SOLID 原则在面向对象设计中的重要组成部分。它规定子类型应该可以替换其基类型,而不会影响程序的清晰度、行为和正确性。

换句话说,如果类 B 是类 A 的子类,那么类型 A 的对象应该可以与类型 B 的对象互换,而不会导致意外的错误或中断功能。

Solid Principles in C#

它确保派生类扩展而不是改变基类的行为,以维护多态性和代码稳定性。遵循 LSP 的开发人员可以构建可靠、一致且易于维护的软件设计,从而无需创建不稳定因素即可替换组件。正确实现 LSP 可以实现可扩展的系统,从而可以在不进行代码重构或产生意外副作用的情况下进行未来的增强。

C# 里氏替换原则 (LSP) 示例

让我们看一个示例来演示 C# 中的里氏替换原则 (LSP)。

示例

编译并运行

输出

 
Error: Penguins cannot fly!
This bird flies.
This bird swims.   

说明

在此示例中,我们演示了里氏替换原则 (LSP) 如何被违反和遵循。在此示例中,PenguinBad 函数继承自 Bird,但在 Fly() 方法中抛出异常,从而违反了 LSP,当被替换时,它无法按预期运行。

之后,在 BirdLSP 抽象类中引入了一个 Move() 方法,该方法允许 FlyingBird 和 PenguinGood 等子类定义自己的适当移动行为,而不会改变预期。

通过这样做,可以保持可扩展性和可靠性,并且子类的对象可以与它们的基类互换而不会遇到问题。Main 方法验证了这两种实现,表明正确实现的运行完美,而糟糕的设计则产生错误。

4. 接口隔离原则 (ISP)

作为第一个仅适用于接口而不是类的原则,接口隔离原则 (ISP) 是面向对象设计中 SOLID 原则的第四个。尽管它侧重于接口而不是类,但它与单一职责原则 (SRP) 有许多相似之处。基本概念是“客户端不应被迫实现它不使用的接口”。

Solid Principles in C#

因此,最好将一个接口分成更小、更专注的接口,这些接口可以满足不同的目的,而不是创建一个包含多个可能不适用于所有类的的大而臃肿(肥胖)的接口。为了确保实现类仅依赖于它们实际使用的方法,每个接口都应具有单一、明确声明的职责。

C# 接口隔离原则 (ISP) 示例

让我们看一个示例来演示 C# 中的接口隔离原则 (ISP)。

示例

编译并运行

输出

 
Bad ISP: The method or operation is not implemented.
Robot is working.   

说明

在提供的 C# 代码中,接口隔离原则 (ISP) 被违反和遵循。在不良示例中,RobotWorker 实现了一个不必要的 Eat() 方法,导致了运行时错误,因为 IWorker 接口强制执行了不相关的职责。

在良好的示例中,接口被分成两部分:IWorkable 用于工作,IEatable 用于进食,BetterRobotWorker 只能实现 IWorkable,避免了不必要的方法。

5. 依赖倒置原则 (DIP)

依赖倒置原则 (DIP) 是 面向对象设计 (OOD) 中的一个基本思想,它是支持软件开发可扩展性和可维护性的五个 SOLID 概念之一。DIP 非常重视使用抽象来减少处理实现细节的低级模块和处理业务逻辑的高级模块之间的直接依赖。

Solid Principles in C#

高级模块应依赖于通用抽象,如接口或抽象类,而不是直接依赖于低级实现。这会减少耦合,提高系统的适应性、可重用性和修改的简便性,而不会损害其当前功能。遵循 DIP 的开发人员可以构建可扩展的设计,从而可以在不更改核心业务逻辑的情况下升级、扩展或替换组件。这使得软件系统更加健壮和适应性强。

C# 依赖倒置原则 (DIP) 示例

让我们看一个示例来演示 C# 中的依赖倒置原则 (DIP)。

示例

编译并运行

输出

 
Sending Email: Violation of DIP!
Sending Email: The following DIP!
Sending SMS: DIP allows flexibility!   

说明

在 C# 代码中,依赖倒置原则 (DIP) 被违反和遵循。不良示例 NotificationBad 和 EmailServiceBad 紧密耦合。因此,任何修改都需要修改类,这会损害可扩展性和灵活性。

由于 NotificationGood 依赖于一个抽象(IMessageService),在良好的示例中可以使用多个实现(EmailServiceGood、SMSServiceGood),而无需修改现有代码。

结论

总而言之,开发人员通过遵循 SOLID 原则来创建不仅模块化和可重用,而且更易于理解和维护的软件。每个概念都有助于提高灵活性和降低复杂性,无论是通过允许在不修改现有代码的情况下进行扩展、确保类具有单一职责,还是使用抽象来解耦依赖。

最终,SOLID 原则通过鼓励对变化的韧性和适应不断变化的需求的能力,为开发更具长期可持续性和效率的软件系统做出了贡献。


下一个主题C# 中的 Stackalloc