单一职责原则 🗂️ 是面向对象编程中的五个设计原则,帮助开发者创建更易于维护、更具灵活性和可扩展性的软件。
这些原则主要关注什么?就是提升软件设计质量,并提倡最佳实践。
所以 SOLID 原则实际上是由五个原则组成的,每个原则的首字母组成了“SOLID”。
让我们来了解一下吧!
1- S => 单一职责准则 (SRP
)
2- O => 开闭原则(也称为“开闭性原则”) (OCP
)
3- L => 里氏替换原则 (LSP
)
4- I => 接口分离原则 (ISP
)
5- D => 依赖反转原则 (DIP
)
这些就是五项原则,现在是时候一个个来看了。
……
1. 单一职责原则-
定义是:一个类应当只有一个变化的理由,也就是说,它应该只负责一个功能职责。
- 好处是:通过让每个类只负责单一功能来简化代码并让代码更易于维护。
如果你觉得不对劲的话,看看下一个例子,否则直接跳过它
//违反单一责任原则
public class UserManager {
public void AddUser(string email , int Id) {
//一些代码...
}
public void SendEmailToUser(int Id) {
//一些代码...
}
public void SendReportToUser(string email) {
//一些代码...
}
}
进入全屏 退出全屏
Q1:你觉得我们应该怎么做才能遵循SR(单一职责)原则?
最后一个例子使用了一个 UserManager
类来实现多种功能(增加用户、发送邮件到用户、发送报告到用户)。为了遵循SRP(单一职责原则),每个类应该只负责一个功能。
//按照单一职责原则
public class UserManager {
public void AddUser(string Email, int Id) {
//待定的代码
}
}
public class EmailService {
public void SendEmail(int Id) {
//待定的代码
}
}
public class ReportService {
public void SendReport(string Email) {
//待定的代码
}
}
点击全屏模式,点击退出全屏
开放封闭原则 (OCP):一个软件实体应该对扩展开放,对修改关闭
-
定义如下:软件实体(如类、模块、函数)应该易于扩展,却不可修改。因此,开放扩展,但封闭修改,即在不修改原代码的前提下增加新的功能。
- 好处:可以添加新的功能而不会影响现有代码,从而减少引入错误的风险
我们来看一个违反 OCP 的例子
public class PaymentService {
public void 处理付款(string 支付类型) {
if(支付类型 == "PayPal") {
//一些代码...
}
if(支付类型 == "CreditCard") {
//一些代码...
}
if(支付类型 == "比特币") {
//一些代码...
}
}
}
全屏,退出全屏
Q2:你觉得最后一个例子违背了开放封闭原则(OCP)吗?
我们现在开始重构代码,使其遵循开闭性原则(OCP)。
// #1: 创建一个接口
public interface IPaymentService {
void process();
}
// #2: 实现具体的支付类
public class PayPalPayment : IPaymentService {
public void process() {
//一些具体的代码..
}
}
public class CreditCardPayment : IPaymentService {
public void process() {
//一些具体的代码..
}
}
切换到全屏模式 退出全屏
就这样,我们让代码遵循了OCP。如何做到的?!
在实现OCP之前,我们不得不在代码中添加一个if语句来检查支付类型。重构之后,遵循了OCP,我们只需添加一个处理支付类型的类,而不需要修改现有的代码!
public class BitcoinPayment : IPaymentService {
public void Process() {
//一些示例代码...
}
}
点击这里全屏, 点击这里退出全屏
3.里氏替换原则(LSP)
-
定义:超类的对象可以被子类的对象替换,而不影响程序的正常运行。简单来说,比如说,你有一个叫[A]的超类和一个叫[B]的子类,你就可以在任何使用[A]的地方用[B]来代替。
- 好处:核心代码在添加外部代码或功能时不会受到改变或影响,保持了可维护性。
//父类
public class Bird {
public virtual void Fly() {
Console.WriteLine("这只鸟在飞行");
}
}
//子类 #1
public class sparrow : Bird {
public override void Fly() {
Console.WriteLine("麻雀在飞行");
}
}
//子类 #2
public class Penguin : Bird {
//企鹅当然不会飞... 所以这里传递了错误的信息..
public override void Fly() {
throw new NotImplementedException("企鹅不会飞!");
}
}
你可以按全屏按钮进入全屏模式,再按退出全屏按钮退出全屏模式。
这违反了LSP,因为我们在任何需要Bird
对象的地方使用penguin
类……程序会出现意外情况,因为调用penguin
类的Fly
方法时会抛出异常……企鹅是不能飞的啊 😂
Q3: 猜猜我怎么做到LSP啊?(LSP)
public abstract class Bird {
public abstract void Display();
}
// 创建飞行行为的接口..
public interface IFlyable {
void Fly();
}
// 现在我们不能正确地实现这个子类...
public class sparrow : Bird, IFlyable {
public override void Display () {
Console.WriteLine("这是一只麻雀!!");
}
public void Fly() {
Console.WriteLine("这只麻雀正在飞行");
}
}
// 企鹅不具备飞行能力,因此不需要实现IFlyable接口。
public class penguin : Bird {
public override void Display () {
Console.WriteLine("这是一只企鹅!");
}
}
全屏,退出全屏
4. 接口隔离原则 (ISP)
-
定义:接口隔离原则指出,一个类不应被迫实现它不使用的接口。相反,较大的接口应该被拆分成更小、更具体的接口,使实现这些接口的类只需关心相关的那些方法。
- 好处:避免类实现不必要的方法。使代码更模块化、更易懂。通过专注于特定功能来支持单一职责原则(SRP)。
违反了这个原则就是说:
public interface IAnimal {
void Eat();
void Fly();
void Swim();
}
public class Dog : IAnimal {
public void Eat() {
Console.WriteLine("狗正在吃东西。");
}
public void Fly() {
throw new NotImplementedException(); // 狗不会飞
}
public void Swim() {
Console.WriteLine("狗正在游泳。");
}
}
点击切换到全屏模式, 点击切换回正常模式
最后一个例子违反了接口隔离原则(ISP),因为它迫使Dog类实现与其行为无关或不必要的方法(如Fly)。这产生了几个问题。
我们试着按照这个原则来重构一下吧:
public interface IEater {
void Eat();
}
public interface ISwimmer {
void Swim();
}
public class Dog : IEater, ISwimmer {
public void Eat() {
Console.WriteLine("狗在吃东西。");
}
public void Swim() {
Console.WriteLine("狗在游泳。");
}
}
您可以点击这里进入全屏模式,点击这里退出全屏模式
通过将 IAnimal 接口拆分为更小且更具体的接口,比如 IEater、IFlyer 和 ISwimmer,每个类只需实现与其行为相关的接口。这样可以避免上述问题,同时符合 ISP(接口隔离原则)。这样可以使代码库更清晰、维护更简单且更具灵活性。
依赖倒置原则
-
定义如下:高层模块不应该依赖底层模块。两者都应依赖于抽象概念(如接口或抽象类等)。
- 优点:减少组件间的紧密耦合,使系统更加灵活,更容易进行重构或重置。
就这样吧!继续编程,继续保持出色。回头见!
共同学习,写下你的评论
评论加载中...
作者其他优质文章