在C#中使用依赖注入-三层结构

三层结构是服务端开发中最为基础的一种结构,也是作为简单项目最为常见的一种结构。本文件将对“如何在三层结构中使用依赖注入”进行介绍。

三层结构简述

一般而言,三层结构可以描述为以下形式

1
2
3
4
graph TD
usl(USL 表示层,实现数据的展示操作) --> |调用|bll
bll(BLL 业务逻辑层,对核心业务逻辑进行实现 ) --> |调用|dal
dal(DAL 数据访问层,实现对数据的增删改查操作)

业务需求

本文需要实现的业务需求大致如下:

在控制台中展示学生的信息

代码演练

版本1,不使用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using System;
using System.Collections.Generic;

namespace Use_Dependency_Injection_In_Simple_Three_Layers
{
public static class Demo1
{
public static void Run()
{
Console.WriteLine($"开始运行{nameof(Demo1)}");
var studentBll = new StudentBll();
var students = studentBll.GetStudents();
foreach (var student in students)
{
Console.WriteLine(student);
}
Console.WriteLine($"结束运行{nameof(Demo1)}");
}

public class StudentBll
{
public IEnumerable<Student> GetStudents()
{
var studentDal = new StudentDal();
var re = studentDal.GetStudents();
return re;
}
}

public class StudentDal
{
private readonly IList<Student> _studentList = new List<Student>
{
new Student
{
Id = "11",
Name = "月落"
},
new Student
{
Id = "12",
Name = "Traceless",
}
};

public IEnumerable<Student> GetStudents()
{
return _studentList;
}
}

public class Student
{
public string Id { get; set; }
public string Name { get; set; }

public override string ToString()
{
return $"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}";
}
}
}
}

简要分析。在绝大多数的场景下,这是最不可取的反例做法。详细的原因可以从下文的改造中得出。

版本2,使用接口,使用依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
using System;
using System.Collections.Generic;

namespace Use_Dependency_Injection_In_Simple_Three_Layers
{
public static class Demo2
{
public static void Run()
{
Console.WriteLine($"开始运行{nameof(Demo2)}");
// 使用 StudentDal1
IStudentBll studentBll = new StudentBll(new StudentDal1());
var students = studentBll.GetStudents();
foreach (var student in students)
{
Console.WriteLine(student);
}
// 使用 StudentDal2
studentBll = new StudentBll(new StudentDal2());
students = studentBll.GetStudents();
foreach (var student in students)
{
Console.WriteLine(student);
}
Console.WriteLine($"结束运行{nameof(Demo2)}");
}

public interface IStudentBll
{
IEnumerable<Student> GetStudents();
}

public class StudentBll : IStudentBll
{
private readonly IStudentDal _studentDal;

/**
* 通过构造函数传入一个 IStudentDal 这种方式称为“构造函数注入”
* 使用构造函数注入的方式,使得不依赖于特定的 IStudentDal 实现。
* 只要 IStudentDal 接口的定义不修改,该类就不需要修改,实现了DAL与BLL的解耦
*/
public StudentBll(
IStudentDal studentDal)
{
_studentDal = studentDal;
}

public IEnumerable<Student> GetStudents()
{
var re = _studentDal.GetStudents();
return re;
}
}

public interface IStudentDal
{
IEnumerable<Student> GetStudents();
}

public class StudentDal1 : IStudentDal
{
private readonly IList<Student> _studentList = new List<Student>
{
new Student
{
Id = "12",
Name = "Traceless",
}
};

public IEnumerable<Student> GetStudents()
{
return _studentList;
}
}

public class StudentDal2 : IStudentDal
{
private readonly IList<Student> _studentList = new List<Student>
{
new Student
{
Id = "11",
Name = "月落"
}
};

public IEnumerable<Student> GetStudents()
{
return _studentList;
}
}

public class Student
{
public string Id { get; set; }
public string Name { get; set; }

public override string ToString()
{
return $"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}";
}
}
}
}

简要分析。与版本1相比,通过定义接口和使用构造函数注入实现了BLL和DAL层的解耦。实现了DAL层的切换,这个过程中没有修改StudentBll代码。

版本3,使用Autofac

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using Autofac;
using System;
using System.Collections.Generic;

namespace Use_Dependency_Injection_In_Simple_Three_Layers
{
public static class Demo3
{
public static void Run()
{
Console.WriteLine($"开始运行{nameof(Demo3)}");
// 使用 StudentDal1
var cb = new ContainerBuilder();
cb.RegisterType<StudentBll>().As<IStudentBll>();
cb.RegisterType<StudentDal1>().As<IStudentDal>();
var container = cb.Build();

var studentBll = container.Resolve<IStudentBll>();
var students = studentBll.GetStudents();
foreach (var student in students)
{
Console.WriteLine(student);
}
// 使用 StudentDal2
cb = new ContainerBuilder();
cb.RegisterType<StudentBll>().As<IStudentBll>();
cb.RegisterType<StudentDal2>().As<IStudentDal>();
container = cb.Build();

studentBll = container.Resolve<IStudentBll>();
students = studentBll.GetStudents();
foreach (var student in students)
{
Console.WriteLine(student);
}
Console.WriteLine($"结束运行{nameof(Demo3)}");
}

public interface IStudentBll
{
IEnumerable<Student> GetStudents();
}

public class StudentBll : IStudentBll
{
private readonly IStudentDal _studentDal;

/**
* 通过构造函数传入一个 IStudentDal 这种方式称为“构造函数注入”
* 使用构造函数注入的方式,使得不依赖于特定的 IStudentDal 实现。
* 只要 IStudentDal 接口的定义不修改,该类就不需要修改,实现了DAL与BLL的解耦
*/
public StudentBll(
IStudentDal studentDal)
{
_studentDal = studentDal;
}

public IEnumerable<Student> GetStudents()
{
var re = _studentDal.GetStudents();
return re;
}
}

public interface IStudentDal
{
IEnumerable<Student> GetStudents();
}

public class StudentDal1 : IStudentDal
{
private readonly IList<Student> _studentList = new List<Student>
{
new Student
{
Id = "12",
Name = "Traceless",
}
};

public IEnumerable<Student> GetStudents()
{
return _studentList;
}
}

public class StudentDal2 : IStudentDal
{
private readonly IList<Student> _studentList = new List<Student>
{
new Student
{
Id = "11",
Name = "月落"
}
};

public IEnumerable<Student> GetStudents()
{
return _studentList;
}
}

public class Student
{
public string Id { get; set; }
public string Name { get; set; }

public override string ToString()
{
return $"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}";
}
}
}
}

简要分析。与版本2相比,只修改了Run中的代码。因为在实际项目中,类之间的依赖关系错综复杂,有时特定的类需要注入多个接口,如果采用版本2的做法,则需要频繁修改new的过程。使用Autofac实现自动的依赖注入,无需自行管理实例,更为方便。

版本3需要通过nuget安装Autofac

总结

使用依赖注入,可以实现代码之间的解耦。通过解耦,可以实现代码之间的相互独立。使得代码的影响面变小,更加可控。

本文示例代码地址

教程链接