Tutorialsteacher

关注我们

文章
  • C#
  • C# 面向对象编程
  • ASP.NET Core
  • ASP.NET MVC
  • LINQ
  • 控制反转 (IoC)
  • Web API
  • JavaScript
  • TypeScript
  • jQuery
  • Angular 11
  • Node.js
  • D3.js
  • Sass
  • Python
  • Go lang
  • HTTPS (SSL)
  • 正则表达式
  • SQL
  • SQL Server
  • PostgreSQL
  • MongoDB
  • C# - 入门
  • C# - 版本历史
  • C# - 第一个程序
  • C# - 关键词
  • C# - 类和对象
  • C# - 命名空间
  • C# - 变量
  • C# - 隐式类型变量
  • C# - 数据类型
  • 数字
  • 字符串
  • DateTime
  • 结构体
  • 枚举
  • StringBuilder
  • 匿名类型
  • 动态类型
  • 可空类型
  • C# - 值类型和引用类型
  • C# - 接口
  • C# - 运算符
  • C# - if else 语句
  • C# - 三元运算符 ?
  • C# - Switch 语句
  • C# - For 循环
  • C# - While 循环
  • C# - Do-while 循环
  • C# - 分部类
  • C# - Static 关键字
  • C# - 数组
  • 多维数组
  • 交错数组
  • C# - 索引器
  • C# - 泛型
  • 泛型约束
  • C# - 集合
  • ArrayList
  • List
  • SortedList
  • Dictionary
  • Hashtable
  • Stack
  • Queue
  • C# - 元组
  • C# - 值元组
  • C# - 内置异常
  • 异常处理
  • throw 关键字
  • 自定义异常
  • C# - 委托
  • Func 委托
  • Action 委托
  • Predicate 委托
  • 匿名方法
  • C# - 事件
  • C# - 协变
  • C# - 扩展方法
  • C# - 流 I/O
  • C# - File 类
  • C# - FileInfo 类
  • C# - 对象初始化器
  • OOP - 概述
  • 面向对象编程
  • 抽象
  • 封装
  • 关联与组合
  • 继承
  • 多态
  • 方法重写
  • 方法隐藏
  • C# - SOLID 原则
  • 单一职责原则
  • 开闭原则
  • 里氏替换原则
  • 接口隔离原则
  • 依赖倒置原则
  • 设计模式
  • 单例模式
  • 抽象工厂模式
  • 工厂方法模式
Entity Framework Extensions - 提升 EF Core 9
  批量插入
  批量删除
  批量更新
  批量合并

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`类具有以下职责:

  1. 保存学生的属性,如`StudentId`、`FirstName`、`LastName`和`DoB`。
  2. 保存一个新学生,或更新数据库中现有学生。
  3. 如果学生未订阅任何课程,则从数据库中删除现有学生。
  4. 根据课程类型应用业务规则来订阅课程。
  5. 处理课程支付。
  6. 成功注册后向学生发送确认邮件。
  7. 将每个活动记录到控制台。

如果上述任何职责发生变化,那么我们将不得不修改`Student`类。例如,如果你需要添加一个新属性,那么我们需要修改`Student`类。或者,如果数据库需要更改,可能从本地服务器迁移到云端,那么你需要更改`Student`类的代码。或者,如果你需要更改业务规则(验证)才能删除学生或订阅课程,或者将日志记录介质从控制台更改为文件,那么在所有这些情况下,你都需要更改`Student`类的代码。因此,由于它有许多职责,你有很多理由修改代码。

SRP告诉我们一个类只有一个修改的原因。让我们根据SRP修改`Student`类,其中我们将为`Student`类只保留一项职责,并将其他职责抽象化(委托)给其他类。

从上面提到的每个职责开始,并决定我们是否应该将其委托给其他类。

  1. `Student`类应包含所有特定于学生的属性和方法。除了`Subscribe()`方法,所有属性和方法都与学生相关,因此保留所有属性。
  2. `Save()`和`Delete()`方法也特定于学生。尽管它使用Entity Framework进行CRUD操作,这是修改`Student`类的另一个原因。我们应该将底层的EF代码移动到另一个类中,以执行所有数据库操作,例如,应该为`Student`的所有CRUD操作创建`StudentRepository`类。这样,如果数据库端有任何更改,我们可能只需要修改`StudentRepository`类。
  3. `Subscribe()`方法更适合`Course`类,因为课程可以根据课程类型有不同的订阅规则。因此,将`Subscribe()`方法移动到`Course`类是理想的选择。
  4. 发送确认邮件也是`Subscribe()`方法的一部分,所以它现在将成为`Course`类的一部分。尽管如此,我们将创建一个单独的`EmailManager`类来发送邮件。
  5. 在这里,所有活动都使用硬编码的`Console.WriteLine()`方法记录到控制台。日志记录需求的任何更改都将导致`Student`类发生更改。例如,如果管理员决定将所有活动记录到文本文件中,那么你需要更改`Student`类。因此,最好创建一个单独的`Logger`类,负责所有日志记录活动。

现在,看看根据上述SRP考虑因素应用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和关注点分离原则增加了内聚性并降低了耦合度。

延伸阅读
  • 整洁代码:单一职责原则
  • 认为你理解了单一职责原则
TUTORIALSTEACHER.COM

TutorialsTeacher.com 是您权威的技术教程来源,旨在通过循序渐进的方法,指导您掌握各种网络和其他技术。

我们的内容帮助所有水平的学习者轻松快速地学习技术。通过访问此平台,您承认您已阅读并同意遵守我们的使用条款和隐私政策,这些条款旨在保护您的体验和隐私权。

[email protected]

关于我们使用条款隐私政策
copywrite-symbol

2024 TutorialsTeacher.com. (v 1.2) 版权所有。