Summary
Object-oriented design (OOD) helps us develop high-performance, easy-to-scale, and reusable programs. Among them, Ood has an important idea that is the dependency inversion principle (DIP), which extends the concepts of IOC, DI, and IOC containers. Through this article we will study these concepts together and clarify the subtle relationship between them.
Directory
- Objective
- Dependency inversion principle (DIP)
- Control Inversion (IoC)
- Dependency Injection (DI)
- IOC container
- Summarize
Objective
For most side dishes, is there any momentary fossilization when you hear about the nouns of dips, IOC, DI, and IOC containers? In fact, these "tall" nouns, it is not so difficult to understand, the key is to get started. As long as we get started, and then gradually, in time, naturally.
Well, let's start with a little bit more on these concepts.
Dependency inversion principle (DIP): A principle of software architecture design (abstract concept).
Inversion of Control (IoC): A way of reversing flows, dependencies, and interfaces (the specific implementation of the dip).
Dependency Injection (DI): an IOC implementation that reverses dependencies (how IOC is implemented).
IOC container: A framework that relies on injection to map dependencies, manage object creation, and the life cycle (di framework).
Oh! Maybe you're trying to break your brains out for these unfamiliar concepts. But it doesn't matter, next I will for you one by one lay bare this one of the mystery.
Dependency inversion principle (DIP)
Before we start talking about concepts, let's look at an example of life.
Figure 1 ATM and bank card
I believe most of the friends who have taken money have deep feelings, as long as there is a card, any bank can withdraw money from any ATM. In this scenario, the ATM is equivalent to a high-level module, while a bank card is equivalent to a lower module. The ATM defines a socket (interface) that is used by all bank cards for insertion. That is, the ATM does not depend on the specific type of bank card. It only needs to define the bank card specifications (interface), all the cards that implement this specification can be used on the ATM. This is true of real life, especially in software development. The dependency inversion principle, which transforms dependencies, does not depend on the implementation of low-level modules, whereas low-level modules rely on interfaces defined by the upper modules. In layman's parlance, the high-level module defines the interface, and the lower module is responsible for implementation.
Bob Martins's definition of dip:
High-level modules should not be dependent on lower-layer modules, and both should be dependent on abstraction.
Abstractions should not be dependent on implementations, and implementations should be dependent on abstractions.
If the examples in your life are not enough to explain the importance of the dependency inversion principle, then we will use the software development scenario to understand why the dependency inversion principle is used.
Scenario one relies on no inversion (low-level module-defined interface, high-layer module is responsible for implementation)
From this, we find that the classes of the high-level modules depend on the interfaces of the lower modules. Therefore, the low-level modules need to take into account all the interfaces. If a new low-level module class is present, the upper module needs to modify the code to implement the interface of the new low-layer module. In this way, the principle of open closure is undermined.
Scene two dependency inversion (high-level module defines the interface, lower layer module is responsible for implementation)
In this diagram, we find that the high-level module defines an interface that is no longer directly dependent on the lower-level module, which is responsible for implementing the interface defined by the high-layer module. This way, when a new low-level module is implemented, you do not need to modify the code of the upper module.
Thus, we can summarize the advantages of using a dip:
The system is more flexible: You can modify part of the code without affecting other modules.
The system is more robust: you can modify a portion of the code without crashing your system.
The system is more efficient: the components are loosely coupled and reusable, improving development efficiency.
Control Inversion (IoC)
Dip is a software design principle that only tells you how to rely on the two modules, but it doesn't tell you how to do it. The IOC is a software design pattern that tells you what to do to decouple the interdependent modules. Control inversion (IoC), which provides abstractions for interdependent components, and gives the dependency (low-level module) object to a third party (system) to control that the dependent object is not obtained directly through new in the class of the dependent module. In the example of Figure 1, we can see that the ATM itself does not insert a specific bank card (ICBC card, ABC card, etc.), but the card work to people to control, that is, we decide to insert what kind of bank card to withdraw money. We also deepen our understanding through scenarios in the software development process.
Software Design principles: Principles provide guidance for us, it tells us what is right and what is wrong. It doesn't tell us how to solve the problem. It just gives some guidelines so that we can design good software to avoid bad design. Some common principles, such as dry, OCP, dip, etc.
Software Design Patterns: Patterns are some of the reusable solutions that are summed up in the software development process, which can solve some practical problems. Some common patterns, such as Factory mode, singleton mode, and so on.
To be a friend of the website of the network will face such a problem: order storage. Assume that the system was initially designed with a SQL Server database. Usually we define a Sqlserverdal class that is used to read and write the database.
public class sqlserverdal{public void Add () { Console.WriteLine ("Add an order in the database!");} }
Then we define an order class, which is responsible for the logical processing of orders. Because the order is to be put into storage, it relies on the operation of the database. So in the order class, we need to define the variables of the Sqlserverdal class and initialize them.
public class order{ private readonly sqlserverdal dal = new Sqlserverdal ();//Add a private variable the object that holds the database operation public void Add ( ) { dal. Add (); }}
Finally, we write a console program to test the results.
Using system;using system.collections.generic;using system.linq;using system.text;namespace DIPTest{ class Program { static void Main (string[] args) { Order order = New Order (); Order. Add (); Console.read ();}}}
Output Result:
OK, the result looks pretty good! When you were smug, the boss came over. "Xiao Liu, just a client over the phone to say the database to change to access", "for you, should be small case!" The boss added. With a proud and tangled mood, we think about the idea of changing the code.
Because of the Access database, the Sqlserverdal class must not be used. Therefore, we need to define a new Accessdal class that is responsible for the operation of the Access database.
public class accessdal{public void Add () { Console.WriteLine ("Add a record in the Access database!") "); }}
Then, look at the code in the Order class. Because the object of the Sqlserverdal class is directly referenced in the Order class. So you also need to change the reference to Accessdal object.
public class order{ private readonly accessdal dal = new Accessdal ();//Add a private variable the object that holds the database operation public void Add () { dal. Add (); }}
Output Result:
Cost Dickens, the program finally ran up! Imagine if the next time a customer wants to switch to a MySQL database, do we have to re-modify the code?
Obviously, this is not a good design, with high coupling between components and poor scalability, it violates the dip principle. The high-level module order class should not rely on the low-layer module sqlserverdal,accessdal, which should be dependent on abstraction. So can we optimize the code through the IOC? The answer is yes. The IOC has 2 common implementations: Dependency Injection and service positioning. Among them, dependency injection is the most widely used. Below we will take a closer look at Dependency injection (DI) and learn to use it.
Dependency Injection (DI)
An important way to control inversion (IoC) is to transfer the creation and binding of dependent objects to the outside of the dependent object class. In the above example, the Order class relies on the creation and binding of the object Sqlserverdal within the order class. It turns out that this method is not advisable. Now that you cannot bind a dependency directly inside the order class, how do you pass a reference to the Sqlserverdal object to the order class for use?
Dependency Injection (DI), which provides a mechanism to pass a reference to a dependency (low-level module) object to a dependent (high-layer Module) object. With Di, we can pass a reference to the Sqlserverdal object to the Order class object outside the Order class. So how is it implemented?
Method one constructor injection
constructor function injection, without a doubt, passes the dependency through the constructor function. Therefore, the parameters of the constructor must be used to receive a dependent object. So what is the type of the parameter? What is the type of the specific dependent object? Or is it an abstract type? Based on the dip principle, we know that high-level modules should not be dependent on low-layer modules, and both should be dependent on abstraction. Then the parameters of the constructor should be an abstract type. Let's go back to the question above, how do you pass a reference to the Sqlserverdal object to the order class for use?
First, we need to define Sqlserverdal's abstract type idataaccess and declare an Add method in the Idataaccess interface.
public interface idataaccess{ void Add ();
Then in the Sqlserverdal class, implement the Idataaccess interface.
public class Sqlserverdal:idataaccess {public void Add () { Console.WriteLine ("Add an order in the database! "); } }
Next, we also need to modify the Order class.
public class Order { Private idataaccess _ida;//define a private variable save abstract //constructor inject public Order (idataaccess ida) { _ida = ida;//Pass Dependent } public void Add () { _ida. Add (); }}
OK, let's write one more console program.
Using system;using system.collections.generic;using system.linq;using system.text;namespace DIPTest{ class Program { static void Main (string[] args) { sqlserverdal dal = new Sqlserverdal ();//Create dependent objects externally Order order = New Order (DAL);//inject dependent order by constructor . Add (); Console.read ();}}}
Output Result:
As we can see from the above, we implement the creation and binding of the dependent object Sqlserverdal object to the outside of the order class, thus relieving the coupling relationship between the Sqlserverdal and the Order class. When our database is replaced with an Access database, you can implement the operation of an Access database by defining a Accessdal class and then externally rebinding the dependencies without modifying the Order class internal code.
Define the Accessdal class:
public class accessdal:idataaccess{public void Add () { Console.WriteLine ("Add a record in the Access database!") "); }}
Then rebind the dependencies in the console program:
Using system;using system.collections.generic;using system.linq;using system.text;namespace DIPTest{ class Program { static void Main (string[] args) { accessdal dal = new Accessdal ();//Create dependent object on external Order Order = New Order (DAL);//inject dependent order by constructor . Add (); Console.read ();}}}
Output Result:
Obviously, we don't need to modify the code of the Order class to complete the porting of the Access database, which undoubtedly embodies the subtlety of the IOC.
Method two-attribute injection
As the name implies, attribute injection is passing dependencies through attributes. Therefore, we first need to define a property in the dependency class order:
public class Order { private idataaccess _ida;//defines a private variable to hold the abstract//attribute, accepting the dependency public idataaccess Ida { set {_ida = value;} get {return _ida;} } public void Add () { _ida. Add (); } }
Then, in the console program, assign a value to the attribute to pass the dependency:
Using system;using system.collections.generic;using system.linq;using system.text;namespace DIPTest{ class Program { static void Main (string[] args) { accessdal dal = new Accessdal ();//Create dependent object on external Order Order = New Order (); Order. Ida = dal;//assigns a value order to a property . Add (); Console.read ();}}}
We can get the same results as above.
Method three-Interface injection
Interface injection is a bit more complex than constructor injection and attribute injection, and is not commonly used. The specific idea is to define an interface that contains a method for setting dependencies. It then relies on the class to inherit and implement this interface.
First define an interface:
Public interface Idependent { void setdependence (idataaccess ida);//Set dependency}
The dependency class implements this interface:
public class Order:idependent { Private idataaccess _ida;//define a private variable save abstract //Implement Interface public void Setdependence (idataaccess ida) { _ida = Ida; } public void Add () { _ida. Add (); } }
The console program passes dependencies through the Setdependence method:
Using system;using system.collections.generic;using system.linq;using system.text;namespace DIPTest{ class Program { static void Main (string[] args) { accessdal dal = new Accessdal ();//Create dependent object on external Order Order = New Order (); Order. Setdependence (DAL);//Pass dependent order. Add (); Console.read ();}}}
We can also get the output of the above results.
IOC container
In all of the previous examples, we created the dependent object manually and passed the reference to the dependent module. Like what:
Sqlserverdal dal = new Sqlserverdal ();//Create dependent objects externally order order = New Order (DAL);//inject dependency through constructor function
For large projects, there are more components that depend on each other. If you create and inject dependencies manually, you are obviously inefficient and often have an uncontrolled scene. For this reason, the IOC container was born. The IOC container is actually a DI framework that simplifies our workload. It contains the following features:
- Dynamically create and inject dependent objects.
- Manage the object life cycle.
- mapping dependencies.
At present, the more popular IOC containers are the following:
1. ninject:http://www.ninject.org/
2. Castle windsor:http://www.castleproject.org/container/index.html
3. autofac:http://code.google.com/p/autofac/
4. structuremap:http://docs.structuremap.net/
5. unity:http://unity.codeplex.com/
NOTE: MEF should not be an IOC container, according to the Xu Shaoxia of the park friend. I looked again at some of the data and felt that MEF was a bit reluctant as an IOC container, and that its main role was to extend the application to avoid generating fragile hard dependencies.
6. mef:http://msdn.microsoft.com/zh-cn/library/dd460648.aspx
In addition, the park friend Aixuexi proposed that Spring.net is also a relatively popular IOC container.
7. spring.net:http://www.springframework.net/
The park friend Wdwwtzy also recommended a nice IOC container:
8. lightinject:http://www.lightinject.net/(recommended for use with Chrome browser)
Taking Ninject as an example, we also implement the function of [method-constructor injection].
The Ninject assembly is added to the project first, and is introduced using the using directive.
Using Ninject;
The IOC container then registers the binding dependency:
Standardkernel kernel = new Standardkernel (); kernel. Bind<idataaccess> (). To<sqlserverdal> ();//Registration dependent
Next, we get the desired order object (injected with the dependent object):
Order order = kernel. Get<order> ();
Below, we write a complete console program
Using system;using system.collections.generic;using system.linq;using system.text;using Ninject;namespace DIPTest{ Class Program { static void Main (string[] args) { Standardkernel kernel = new Standardkernel ();//Create IOC container kernel. Bind<idataaccess> (). To<sqlserverdal> ();//registration depends on order order = kernel. Get<order> ();//Gets the target object Order. Add (); Console.read ();}}}
Output Result:
Using the IOC container, we have also implemented this function.
Summarize
In this article, I try to explain in the most popular way, hoping to help you understand these concepts. Let's summarize here: Dip is a kind of idea of software design, IOC is a kind of software design pattern based on dip. Di is one of the specific implementations of the IOC and is most widely used. The IOC container is the framework that the Di constructor injects, which manages the life cycle of the dependency and the mapping relationship.
Source: http://www.codeceo.com/article/dip-ioc-di-ioc-learn.html
Deep understanding of dip, IOC, DI, and IOC containers