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 语言
  • 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# - Tuple
  • C# - ValueTuple
  • 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原则的最后一个原则。它有助于实现松耦合。

依赖反转原则指出:

高层模块不应该依赖于低层模块。两者都应该依赖于抽象。

现在的问题是,什么是高层模块和低层模块,什么是抽象?

高层模块是一个使用其他模块(类)来执行任务的模块(类)。低层模块包含某个特定任务的详细实现,可供其他模块使用。高层模块通常是应用程序的核心业务逻辑,而低层模块是输入/输出、数据库、文件系统、Web API或其他与用户、硬件或其他系统交互的外部模块。

抽象是非具体的事物。抽象不应该依赖于细节,而细节应该依赖于抽象。例如,抽象类或接口包含需要在具体类中实现的方法声明。这些具体类依赖于抽象类或接口,反之则不然。

现在,我们如何知道一个类依赖于另一个类?

如果一个类创建了另一个类的对象,你就可以识别出它依赖于另一个类。你可能需要添加命名空间的引用才能编译或运行代码。

让我们使用以下示例来理解DIP

示例:没有DIP的类
public class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DoB { get; set; }

    //tight coupling
    private StudentRepository _stdRepo = new StudentRepository();
       
    public Student()
    {

    }

    public void Save()
    {
        _stdRepo.AddStudent(this);
    }
}

public class StudentRepository 
{
    public void AddStudent(Student std)
    {
        //EF code removed for clarity
    }

    public void DeleteStudent(Student std)
    {
        //EF code removed for clarity
    }

    public void EditStudent(Student std)
    {
        //EF code removed for clarity
    }
        
    public IList<Student> GetAllStudents()
    {
        //EF code removed for clarity
    }
}

上面的Student类创建了StudentRepository类的对象,用于对数据库执行CRUD操作。因此,Student类依赖于StudentRepository类进行CRUD操作。Student类是高层模块,StudentRepository类是低层模块。

这里的问题是,Student类使用new关键字创建了具体StudentRepository类的对象,导致两者紧密耦合。这会导致以下问题:

  • 在所有地方都使用new关键字创建对象是重复的代码。对象创建不在一个地方。这违反了“不要重复你自己”(DRY)原则。如果StudentRepository类的构造函数发生变化,那么我们需要在所有地方进行更改。如果对象创建在一个地方,那么维护代码将很容易。
  • 使用new创建对象也使得单元测试不可能。我们无法单独对Student类进行单元测试。
  • StudentRepository类是一个具体类,因此该类中的任何更改都需要同时更改Student类。

DIP指出高层模块不应该依赖于低层模块。两者都应该依赖于抽象。这里,抽象意味着使用接口或抽象类。

以下是将DIP原则应用于上述示例的结果。

示例:应用DIP后的类
public class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DoB { get; set; }

    private IStudentRepository _stdRepo;

    public Student(IStudentRepository stdRepo)
    {
        _stdRepo = stdRepo;
    }

    public void Save()
    {
        _stdRepo.AddStudent(this);
    }
}

public interface IStudentRepository
{
    void AddStudent(Student std);
    void EditStudent(Student std);
    void DeleteStudent(Student std);
        
    IList<Student> GetAllStudents();
}

public class StudentRepository : IStudentRepository
{
    public void AddStudent(Student std)
    {
        //code removed for clarity
    }

    public void DeleteStudent(Student std)
    {
        //code removed for clarity
    }

    public void EditStudent(Student std)
    {
        //code removed for clarity
    }

    public IList<Student> GetAllStudents()
    {
        //code removed for clarity
    }
}

上面的StudentRepository类实现了IStudentRepository接口。这里,IStudentRepository是学生相关数据的CRUD操作的抽象。StudentRepository类提供了这些方法的实现,因此它依赖于IStudentRepository接口的方法。

Student类不使用new关键字创建StudentRepository类的对象。构造函数需要一个IStudentRepository类的参数,该参数将从调用代码传入。因此,它也依赖于抽象(接口),而不是低层具体类(StudentRepository)。

这将创建松耦合,并使每个类都可以进行单元测试。Student类的调用者可以传入实现IStudentRepository接口的任何类的对象,因此不会与特定的具体类绑定。

public static void Main(string[] args)
    {
	    //for production
	    Student std1 = new Student(new StudentRepository);

	    //for unit test
	    Student std2 = new Student(new TestStudentRepository);
    }
}

你可以使用工厂类来创建对象,而不是手动创建,这样所有对象创建都将集中在一个地方。

示例:使用工厂类的DIP
public class RepositoryFactory
{
    public static IStudentRepository GetStudentRepository() 
    {
        return new StudentRepository();
    }

    public static IStudentRepository GetTestStudentRepository() 
    {
        return new TestStudentRepository();
    }
}

public class Program
{
    public static void Main(string[] args)
    {
	    //for production
	    Student std1 = new Student(RepositoryFactory.GetStudentRepository());

	    //for unit test
	    Student std2 = new Student(RepositoryFactory.TestGetStudentRepository());
    }
}

建议使用依赖注入和IoC容器来创建低层类的对象并将其传递给高层类。

TUTORIALSTEACHER.COM

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

我们的内容帮助所有级别的学习者轻松快速地学习技术。访问此平台即表示您已审阅并同意遵守我们的使用条款和隐私政策,这些条款和政策旨在保障您的体验和隐私权。

[email protected]

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

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