Theoretical basis of DI
rely on
What is dependency? In short, it is the relationship between classes. Class dependency occurs when a class needs another class to work together
This dependency between classes is coupling. We can't eliminate the coupling (unavoidable), but we can make this coupling dependency clear and easy to manage through DI
Example: Classic three-tier architecture
UI: Interface
BLL: business logic processing
DAL: data access
Code of data access DAL layer:
/// <summary> ///Storage /// </summary> public class TestRepository { public string GetName(long id) { return "Zhang San"; } }
Business layer (BLL) code:
/// <summary> ///Logical processing /// </summary> public class TestService { private readonly TestRepository m_testRepository; public TestService() { m_testRepository = new TestRepository(); } public string GetName(long id) { var name = m_testRepository.GetName(id); return name.Name; } }
TestService needs to rely on TestRepository, which is a tight coupling. Once the TestRepository is changed, the code of TestService will also need to be changed. If the amount of changes is large, it will explode
Interface oriented programming
Facing is to realize a design principle: rely on abstraction rather than concrete implementation.
For example, the above example adds an interface to the DAL layer, such as ITestRepository, and the implementation code is as follows:
/// <summary> ///Warehousing interface /// </summary> public interface ITestRepository { string GetName(long id); }
At this time, the TestService of BLL layer can rely on ITestRepository, for example:
/// <summary> ///Logical processing /// </summary> public class TestService { private readonly ITestRepository m_testRepository; public TestService() { m_testRepository = new TestRepository(); } public string GetStuName(long id) { var name = m_testRepository.GetName(id); return name.Name; } }
There are two benefits of this, one is low coupling and the other is clear responsibility.
For example, those who write the storage layer only need to implement the interface of ITestRepository, regardless of who calls me. Those who write the BLL layer Service only need to call the interface in ITestRepository, regardless of how the interface is implemented. Another example:
If the person who writes the TestRepository writes badly, it's better to rewrite it. At this time, you can rewrite an itesrepository interface to implement newtestrepository. After rewriting, the Service layer call only needs to replace the corresponding implementation:
public TestService() { m_testRepository = new NewTestRespository(); }
But in fact, this problem is also very big. For example, if more than N services are used in a huge system, it is still troublesome to modify more than N places.
The reason is that as mentioned above, this is a dependency relationship. A Service depends on a Repository. Is there a way to reverse this control relationship? When a Service needs to use a Repository, is there any way to inject the Repository I need into me?
Of course, this is the dependency injection we will implement.
After using dependency injection, you will find that after rewriting the new warehouse newtestrepository, the BLL business logic layer (TestService) does not need to change any code. All services do not need to be changed one by one. Modify the rules directly during injection. Do not inject the old warehouse. Just inject the new warehouse directly
Interface Oriented Architecture:
name | duty | give an example |
---|---|---|
Interface layer (UI) | Responsible for displaying data | TestController |
Business logic abstraction layer (InterfaceBLL) | Business logic operation abstract interface | ITestService |
Business logic layer (BLL) | Responsible for business logic operation | TestService |
Data access abstraction layer (InterfaceDAL) | Data access abstract interface | ITestRepository |
Data access layer (DAL) | Responsible for providing data | TestRepository |
What is IoC
IoC, full name of Inversion of Control, namely "control reversal", is a design principle proposed by Martin Fowler
What is DI
DI, fully known as Dependency Injection, that is, Dependency Injection, is one of the design methods to realize IoC.
It is characterized by injecting dependent objects into callers through some techniques. (for example, injecting Repository into Service)
At present, the skills mentioned here mainly refer to the container. First, add all objects that will generate dependencies to the container, such as TestRepository and TestService, and give the allocation permission to the container. When TestService needs to use TestRepository internally, it should not be allowed to create a new one by itself, but inject TestRepository into TestService through the container.
This is the origin of the name "dependency injection".
What is the difference between DI and IoC
Are DI and IoC the same thing? Answer: not a thing.
The difference is also very simple: IOC is a concept, DI is one of the methods to realize IOC, an IOC implementation technology.
IoC is a very broad design concept. Changing a dead variable in the program to read from the configuration file is also a control inversion (from program control to framework control). Changing this configuration to an input text box in the user UI interface is also a control inversion (from framework control to user control).
Therefore, IOC and DI are not the same thing. IOC is a concept and DI is one of the means
AutoFac
Write an AutoFac console application to illustrate how to simply use this container
Program startup process: Main ------ > service ------ > repository
First, establish corresponding interfaces and classes:
Repository:
/// <summary> ///Storage layer interface /// </summary> public interface ITestRepository { /// <summary> ///Get name by ID /// </summary> /// <param name="id"></param> /// <returns></returns> string GetNameById(string id); } public class TestRepository : ITestRepository { public string GetNameById(string id) { return "Zhang San"; } }
Service:
/// <summary> ///Service layer interface /// </summary> public interface ITestService { /// <summary> ///Get name by ID /// </summary> /// <param name="id"></param> /// <returns></returns> string GetNameById(string id); } public class TestService : ITestService { private ITestRepository m_testRepository; //Injection interface public TestService(ITestRepository testRepository) { m_testRepository = testRepository; } public string GetNameById(string id) { return m_testRepository.GetNameById(id); } }
Autofac container initialization and registration
public static class Container { public static Autofac.IContainer Instance; public static void Init() { //Create a new container builder to register components and services var builder = new ContainerBuilder(); Register(builder); //Creating containers with builder Instance = builder.Build(); } private static void Register(ContainerBuilder builder) { builder.RegisterType<TestRepository>().As<ITestRepository>(); builder.RegisterType<TestService>().As<ITestService>(); } }
-
public static IContainer Instance is a singleton container
-
ContainerBuilder is a container constructor defined for AutoFac, through which objects are registered into the container.
-
RegisterType is the most basic registration method encapsulated by AutoFac. The passed in generic type (TestRepository) is the object to be added to the container; As is responsible for binding the type of the registered object, which is generally exposed by the interface type it implements. From the outside of the container, the container object can only be found through the exposed type
Main function
class Program { static void Main(string[] args) { Container.Init(); GetName(); Console.ReadKey(); } private static void GetName() { //Resolve objects from container ITestService service = Container.Instance.Resolve<ITestService>(); string name = service.GetNameById("123"); Console.WriteLine($"name is {name}"); } }
Here is the container Instance. Resolve<ITestService>(); The implementation steps are as follows
1. The container uses ITestService to find the corresponding class (TestService) in the container. After finding it, it will try to instantiate this class. At this time, it is found that the constructor of this class has parameters
2. When the container finds that this parameter is ITestRepository, it goes to the container to find the class corresponding to this type, and then instantiates it and automatically injects it into the TestService class
3. At this point, a simple dependency injection is completed
We can find the registered objects in the Instance container through the breakpoint, which is not shown in the figure above