SOLID:单一职责原则
单一职责原则是SOLID原则的第一个原则。它是面向对象编程的基本原则,决定了我们应该如何设计类。
单一职责原则指出:
换句话说,一个类应该只有一项职责,因此它应该只有一个修改其代码的原因。如果一个类有多个职责,那么将有多个修改该类(代码)的原因。
现在,问题是职责是什么?
一个应用程序可以有许多功能(特性)。例如,一个在线电子商务应用程序有许多功能,如显示产品列表、提交订单、显示产品评级、管理客户的送货地址、管理支付等。除了这些功能,它还验证和持久化产品和客户数据,记录活动以供审计和安全目的,应用业务规则等。你可以将这些点视为功能或特性或职责。任何功能的变化都会导致负责该功能的类发生变化。
让我们检查一下下面的`Student`类有多少职责
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DoB { get; set; }
public string email { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
public void Save()
{
Console.WriteLine("Starting Save()");
//use EF to save student to DB
Console.WriteLine("End Save()");
}
public void Delete()
{
Console.WriteLine("Starting Delete()");
//check if already subscribed courses then don't delete
Console.WriteLine("End Delete()");
}
public IList<Course> Subscribe(Course cs)
{
Console.WriteLine("Starting Subscribe()");
//apply business rules based on the course type
if(cs.Type == "online")
{
//validate
}
else if(cs.type == "live")
{
}
//payment processing code
//save course subscription to DB
//send email confirmation code
Console.WriteLine("End Subscribe()");
}
}
上述`Student`类具有以下职责:
- 保存学生的属性,如`StudentId`、`FirstName`、`LastName`和`DoB`。
- 保存一个新学生,或更新数据库中现有学生。
- 如果学生未订阅任何课程,则从数据库中删除现有学生。
- 根据课程类型应用业务规则来订阅课程。
- 处理课程支付。
- 成功注册后向学生发送确认邮件。
- 将每个活动记录到控制台。
如果上述任何职责发生变化,那么我们将不得不修改`Student`类。例如,如果你需要添加一个新属性,那么我们需要修改`Student`类。或者,如果数据库需要更改,可能从本地服务器迁移到云端,那么你需要更改`Student`类的代码。或者,如果你需要更改业务规则(验证)才能删除学生或订阅课程,或者将日志记录介质从控制台更改为文件,那么在所有这些情况下,你都需要更改`Student`类的代码。因此,由于它有许多职责,你有很多理由修改代码。
SRP告诉我们一个类只有一个修改的原因。让我们根据SRP修改`Student`类,其中我们将为`Student`类只保留一项职责,并将其他职责抽象化(委托)给其他类。
从上面提到的每个职责开始,并决定我们是否应该将其委托给其他类。
- `Student`类应包含所有特定于学生的属性和方法。除了`Subscribe()`方法,所有属性和方法都与学生相关,因此保留所有属性。
- `Save()`和`Delete()`方法也特定于学生。尽管它使用Entity Framework进行CRUD操作,这是修改`Student`类的另一个原因。我们应该将底层的EF代码移动到另一个类中,以执行所有数据库操作,例如,应该为`Student`的所有CRUD操作创建`StudentRepository`类。这样,如果数据库端有任何更改,我们可能只需要修改`StudentRepository`类。
- `Subscribe()`方法更适合`Course`类,因为课程可以根据课程类型有不同的订阅规则。因此,将`Subscribe()`方法移动到`Course`类是理想的选择。
- 发送确认邮件也是`Subscribe()`方法的一部分,所以它现在将成为`Course`类的一部分。尽管如此,我们将创建一个单独的`EmailManager`类来发送邮件。
- 在这里,所有活动都使用硬编码的`Console.WriteLine()`方法记录到控制台。日志记录需求的任何更改都将导致`Student`类发生更改。例如,如果管理员决定将所有活动记录到文本文件中,那么你需要更改`Student`类。因此,最好创建一个单独的`Logger`类,负责所有日志记录活动。
现在,看看根据上述SRP考虑因素应用SRP后重新设计的以下类。
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DoB { get; set; }
public string email { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
public void Save()
{
Logger.Log("Starting Save()");
_studentRepo.Save(this);
Logger.Log("End Save()");
}
public void Delete()
{
Logger.Log("Starting Delete()");
//check if already subscribed courses
_studentRepo.Delete(this);
Logger.Log("End Delete()");
}
}
Public class Logger
{
Public static void Log(string message)
{
Console.WriteLine(message);
}
}
public class StudentRepository()
{
Public bool Save(Student std)
{
Logger.log("Starting Save()");
//use EF to add a new student or update existing student to db
Logger.log("Ending Saving()");
}
public bool Delete()
{
Logger.log("Starting Delete()");
//use EF to delete a student
Logger.Log("Ending Delete()");
}
public bool SaveCourse(Student std, Course cs)
{
Logger.log("Starting SaveCourse()");
//use EF to save a course for a student
Logger.Log("Ending SaveCourse()");
}
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public string Type { get; set; }
public void Subscribe(Student std)
{
Logger.Log("Starting Subscribe()");
//apply business rules based on the course type live, online, offline, if any
if (this.Type == "online")
{
//subscribe to online course
}
else if (this.Type == "live")
{
//subscribe to offline course
}
// payment processing
PaymentManager.ProcessPayment();
//create CourseRepository class to save student and course into StudentCourse table
// send confirmation email
EmailManager.SendEmail();
Logger.Log("End Subscribe()");
}
}
Public class EmailManager
{
Public static void SendEmail(string recEmailed, string senderEmailId, string subject, string message)
{
// smtp code here
}
}
Public class PaymentManger
{
Public static void ProcessPayment()
{
//payment processing code here
}
}
现在,思考上面的类。每个类都有单一职责。`Student`类包含特定于学生相关活动的属性和方法。`Course`类具有课程相关职责。`StudentRepository`具有使用Entity Framework进行学生相关CRUD操作的职责。`Logger`类负责日志记录活动。`EmailManager`类具有邮件相关职责。`PaymentManager`类具有支付相关活动。
通过这种方式,我们将特定职责委托给单独的类,以便每个类只有一个修改的原因。这增加了内聚性并降低了耦合度。
关注点分离
单一职责原则遵循另一个原则,称为关注点分离。
关注点分离建议应用程序应分为不同的部分,其中每个部分处理一个单独的关注点或一组影响程序的信息。这意味着高级业务逻辑应避免处理低级实现。
在我们的示例中,我们将每个关注点分离到单独的类中。最初我们只有一个`Student`类,但后来我们将CRUD操作、日志记录、邮件等每个关注点分离到单独的类中。因此,`Student`类(高级类)不知道CRUD或发送邮件是如何发生的。它只是使用适当的方法,仅此而已。
SRP和关注点分离原则增加了内聚性并降低了耦合度。
- 整洁代码:单一职责原则
- 认为你理解了单一职责原则