C # Programming: Dependency Inversion Principle DIP

I. Introduction

Let's take a look at the traditional three-tier architecture, as shown below:

We can see from the image above: In the traditional three-tier architecture, between the layers are interdependent, UI layer depends on the BLL, BLL layer depends on the DAL. Layered object is to achieve "high cohesion and low coupling." The traditional three-tier architecture is not only the high cohesion and low coupling, a strong dependence of the relationship between the layers, which is a disadvantage of conventional three-tier architecture. This top-down dependency will lead to cascading changes, if low-level changes occur, all of the layers may need to modify the above, and the traditional three-tier architecture is difficult to achieve collaborative development team, because the upper function depending on the implementation of the underlying function, the following functions if there is no development is completed, the upper layer functions can not be performed.

The traditional three-tier architecture did not follow the Dependency Inversion Principle (DIP) to design, so there will be above question.

Second, the Dependency Inversion

Dependency Inversion (DIP): Dependence Inversion Principle Abbreviation, mainly has two meanings:

  1. High-level modules should not depend on low-level modules, both of which should rely on their abstract.
  2. Abstract should not rely on specific, concrete should depend on the abstract.

Let's first words to explain: concrete realization of high-level modules should not depend directly on low-level modules, but should rely on abstract low-level blocks, that is, dependencies between modules through abstract occurring between the implementation class direct dependencies should not happen, they should produce dependencies through interfaces or abstract classes.

In the explained second sentence: abstract class interface or should not depend on the implementation class. For example, if we write the code BLL layer, go directly to achieve the function, wait until after the development is completed and found no use Dependency Inversion principle, this time in class to write the interface depending on the implementation, this is wrong, should first design abstract, then to achieve according to the abstract, it should be oriented programming interface.

We said above, in the traditional three-tier architecture which does not use Dependency Inversion Principle, then the Dependency Inversion principle applied to the traditional three-tier architecture which will happen then? We know that in the traditional three-tier architecture inside, UI layer directly dependent on the BLL, BLL layer is directly dependent on the DAL, since each layer is dependent on the realization of the next layer, so that the time when the underlying changes, it one should also take place on the change, this time can be re-designed three-tier architecture based on Dependency inversion principle.

There should be no direct dependency between UI, BLL, DAL three, it should depend on the interface. Should identify the first interface layer abstracts the DAL IDAL interface layer abstracts the BLL IBLL interface UI so that the interface layer depends on IBLL, implement the BLL IBLL interface. IDAL interfaces depends on the BLL, DAL IDAL implement the interface. As shown below:

We talked about above Dependency Inversion principle, then what Dependency Inversion principle purpose is it?
With the Dependency Inversion principle, we can make our infrastructure more stable, flexible, and better able to respond to changes in demand. Variability with respect to the details of the abstract things are stable. So abstract as the basis to build up infrastructure than to build up the basis of the details of the structure much more stable.

In the traditional three-tier architecture inside, just add an interface layer, we realized the dependency inversion, the purpose is to reduce the coupling between layers. With such an interface layer, three-tier architecture was truly thinking "high cohesion, low coupling".

Dependency Inversion principle is the architecture level, then how to achieve it at the level of code? Let's look at inversion of control.

Third, Inversion of Control

Inversion of Control (IOC): Inversion of Control abbreviation of a reversing flow, and an interface dependent manner, it is traditionally the controller program code directly manipulate objects (created, maintained) to a third party, by Third Party (IOC vessel) to assemble and implement management object assembly.

IOC container, dependency injection framework can also be called, is caused by a dependency injection framework, mainly used to map dependence, create and life cycle management of objects. Is the essence of an object on the IOC container, the program will usually inside all classes are registered in, the use of this class, go directly to resolve from the container inside.

Fourth, the Dependency Injection

Dependency injection (DI): Abbreviation of Dependency Injection. Dependency injection is controlled inversion one implementation, dependency injection purpose is to achieve the inversion of control.

Dependency injection is a tool or a means, the aim is to help us to develop loosely coupled, program maintenance.

DI commonly used methods are the following:

  1. Constructor injection.
  2. Properties injection.
  3. Method Injection.

Wherein constructor injection is the most used, followed by injection property.

A look at the following example: father telling stories to children, just give the father a book, he can shining this book to tell stories to children. Here we first use the most traditional way to achieve it, there is not any design principles and design patterns.

First define a Book class:

namespace DipDemo1
{
    public class Book
    {
        public string GetContent()
        {
            return  " once a mountain, the mountain there was a temple ..... " ;
        }
    }
}

Father then define a class:

using System;

namespace DipDemo1
{
    public class Father
    {
        public void Read()
        {
            Book book = new Book();
            Console.WriteLine ( " father began telling stories to the children " );
            Console.WriteLine(book.GetContent());
        }
    }
}

Then inside the Main method call:

using System;

namespace DipDemo1
{
    class Program
    {
        static void Main(string[] args)
        {
            Father father = new Father();
            father.Read();
            Console.ReadKey();
        }
    }
}

We take a look at the diagram:

We see: Father is directly dependent on the Book category.

Then a change in demand, not a book to my father, Dad newspaper, the father to the children shining newspaper reading newspapers, then how to do it? According to the traditional way, at this time we need to define a class in a newspaper:

namespace DipDemo1
{
    public class NewsPaper
    {
        public string GetContent()
        {
            return  " news " ;
        }
    }
}

At this dependency has changed, because my father to rely on newspapers, which led also to modify Father categories:

using System;

namespace DipDemo1
{
    public class Father
    {
        public void Read()
        {
            // 读书
            // Book book = new Book();
            //Console.WriteLine("爸爸开始给孩子讲故事了");
            //Console.WriteLine(book.GetContent());

            // 报纸
            NewsPaper paper = new NewsPaper();
            Console.WriteLine("爸爸开始给孩子讲新闻");
            Console.WriteLine(paper.GetContent());
        }
    }
}

假设后面需求又变了,又不给报纸了,换成杂志、平板电脑等。需求在不断的变化,不管怎么变化,对于爸爸来说,他一直在读读物,但是具体读什么读物是会发生变化,这就是细节,也就是说细节会发生变化。但是抽象是不会变的。如果这时候还是使用传统的OOP思想来解决问题,那么会导致程序不断的在修改。下面使用工厂模式来优化:

首先创建一个接口:

namespace DipDemo2
{
    public interface IReader
    {
        string GetContent();
    }
}

然后让Book类和NewsPaper类都继承自IReader接口,Book类

namespace DipDemo2
{
    public class Book : IReader
    {
        public string GetContent()
        {
            return "从前有座山,山上有座庙.....";
        }
    }
}

NewsPaper类:

namespace DipDemo2
{
    public class NewsPaper : IReader
    {
        public string GetContent()
        {
            return "王聪聪被限制高消费......";
        }
    }
}

然后创建一个工厂类:

namespace DipDemo2
{
    public static class ReaderFactory
    {
        public static IReader GetReader(string readerType)
        {
            if (string.IsNullOrEmpty(readerType))
            {
                return null;
            }

            switch (readerType)
            {
                case "NewsPaper":
                    return new NewsPaper();
                case "Book":
                    return new Book();
                default:
                    return null;
            }
        }
    }
}

里面方法的返回值是一个接口类型。最后在Father类里面调用工厂类:

using System;

namespace DipDemo2
{
    public class Father
    {
        private IReader Reader { get; set; }

        public Father(string readerName)
        {
            // 这里依赖于抽象
            Reader = ReaderFactory.GetReader(readerName);
        }

        public void Read()
        {
            Console.WriteLine("爸爸开始给孩子讲故事了");
            Console.WriteLine(Reader.GetContent());
        }
    }
}

最后在Main方法里面调用:

using System;

namespace DipDemo2
{
    class Program
    {
        static void Main(string[] args)
        {
            Father father = new Father("Book");
            father.Read();

            Console.ReadKey();
        }
    }
}

我们这时候可以在看看依赖关系图:

这时Father已经和Book、Paper没有任何依赖了,Father依赖于IReader接口,还依赖于工厂类,而工厂类又依赖于Book和Paper类。这里实际上已经实现了控制反转。Father(高层)不依赖于低层(Book、Paper)而是依赖于抽象(IReader),而且具体的实现也不是由高层来创建,而是由第三方来创建(这里是工厂类)。但是这里只是使用工厂模式来模拟控制反转,而没有实现依赖的注入,依赖还是需要向工厂去请求。

下面继续优化代码,这里只需要修改Father类:

using System;

namespace DipDemo3
{
    public class Father
    {
        public IReader Reader { get; set; }

        /// <summary>
        /// 构造函数的参数是IReader接口类型
        /// </summary>
        /// <param name="reader"></param>
        public Father(IReader reader)
        {
            Reader = reader;
        }

        public void Read()
        {
            Console.WriteLine("爸爸开始给孩子讲故事了");
            Console.WriteLine(Reader.GetContent());
        }
    }
}

在Main方法里面调用:

using System;
namespace DipDemo3
{
    class Program
    {
        static void Main(string[] args)
        {
            var f = new Father(new Book());
            f.Read();

            Console.ReadKey();
        }
    }
}

如果以后换成了Paper,需要修改代码:

using System;
namespace DipDemo3
{
    class Program
    {
        static void Main(string[] args)
        {
            // Book
            //var f = new Father(new Book());
            //f.Read();

            // Paprer
            var f = new Father(new Paper());
            f.Read();

            Console.ReadKey();
        }
    }
}

由于这里没有了工厂,我们还是需要在代码里面实例化具体的实现类。如果有一个IOC容器,我们就不需要自己new一个实例了,而是由容器帮我们创建实例,创建完成以后在把依赖对象注入进去。

我们在来看一下依赖关系图:

下面我们使用Unity容器来继续优化上面的代码,首先需要在项目里面安装Unity,直接在NuGet里面搜索即可:

这里只需要修改Main方法调用即可:

using System;
using Unity;

namespace UnityDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 创建容器
            var container = new UnityContainer();

            // 扫描程序集、配置文件
            // 在容器里面注册接口和实现类,创建依赖关系
            container.RegisterType<IReader, Book>();

            // 在容器里面注册Father
            container.RegisterType<Father>();

            // 从容器里拿出要使用的类,容器会自行创建father对
            // 还会从容器里去拿到他所依赖的对象,并且注入进来
            // 
            var father = container.Resolve<Father>();

            // 调用方法
            father.Read();
            Console.ReadKey();
        }
    }
}

Guess you like

Origin www.cnblogs.com/dotnet261010/p/12289609.html