在学习这部分知识之前,我先提几个问题:

1. 控制反转(IOC:Inversion of Control),难道是A类调用B类,改写成B类调用A类么?不合适啊

2. 依赖注入(DI:Dependency Injection),我觉得只要看到一个函数的参数中的类型有接口或抽象类类型的,那么它一定用到了依赖注入

3. 控制反转和依赖注入是什么关系?怎么总把它俩放在一起讨论?

4. 容器和他们是什么关系?

 

先说说什么是依赖倒置

依赖倒置是Robert Martin大师提出的面向对象涉及原则,简单说来只有两条:

1. 上层模块不应该依赖于下层模块,他们共同依赖于一个抽象

2. 抽象不能依赖于具象,具象依赖于抽象

 

理解:上层为使用者,下层为被使用者;这就会导致上层模块依赖于下层木块,当下层模块做改动,避免不了的会影响到上层模块,从而导致系统的不稳定性。相对于具象(这里理解为具体实现),抽象更加稳定(这里理解为不轻易改动)。

 

 

再说说控制反转

控制反转并不是将控制器倒置,而是将控制权转移,所以应该叫控制转移更贴切一些

 

 

再说说控制反转和依赖注入之间的关系

控制反转只是一个思想,一个概念,而依赖注入是控制反转的一种实现,当然还有依赖查询,这也是一种实现,但我们今天不讨论。

依赖注入是转移了对象的创建权。

 

容器的概念

如果依赖注入可以算作容器的一部分功能,容器再运行期间通过依赖注入的方式,动态的将某种依赖关系注入到对象中。

 

我们通过【动物们学唱歌】的程序来解释一下以上概念:

首先是只有一只猫,它要在森林中唱歌,代码怎么写?要实现三个类对吧,Cat、Forest、MainApp主函数类

 

public class Cat
    {
        public void Sing(Forest forest)
        {
            Console.WriteLine("I'm a Cat,I like Singing in the "+ forest.Space);
        }
    }

    public class Forest
    {
        public string Space { get { return "Forest"; } }
    }

    public class MainClass
    {
        public static void RunApp()
        {
            Cat cat = new Cat();
            Forest forest = new Forest();
            cat.Sing(forest);
        }
    }
public class Cat
    {
        public void Sing(Forest forest)
        {
            Console.WriteLine("I'm a Cat,I like Singing in the "+ forest.Space);
        }
    }

    public class Forest
    {
        public string Space { get { return "Forest"; } }
    }

    public class MainClass
    {
        public static void RunApp()
        {
            Cat cat = new Cat();
            Forest forest = new Forest();
            cat.Sing(forest);
        }
    }
MainClass.RunApp();

运行结果:

依赖倒置的分层架构 DDD 代码github 依赖倒置和控制反转_依赖关系

以上三个类的依赖关系图为:

依赖倒置的分层架构 DDD 代码github 依赖倒置和控制反转_控制反转_02

如果小狗看见了,它想在草地上唱歌怎么办?我们是不是应该要增加俩个类 Dog、Lawn

public class Cat
    {
        public void Sing(Forest forest)
        {
            Console.WriteLine("I'm a Cat,I like Singing in the "+ forest.Space);
        }
    }
    public class Dog
    {
        public void Sing(Lawn lawn)
        {
            Console.WriteLine("I'm a Dog,I like Singing in the " + lawn.Space);
        }
    }

    public class Forest
    {
        public string Space { get { return "Forest"; } }
    }
    public class Lawn
    {
        public string Space { get { return "Lawn"; } }
    }

    public class MainClass
    {
        public static void RunApp()
        {
            Cat cat = new Cat();
            Forest forest = new Forest();
            cat.Sing(forest);

            Dog dog = new Dog();
            Lawn lawn = new Lawn();
            cat.Sing(forest);
        }
    }

 

运行结果

依赖倒置的分层架构 DDD 代码github 依赖倒置和控制反转_控制反转_03

这几个类的依赖关系图:

依赖倒置的分层架构 DDD 代码github 依赖倒置和控制反转_依赖注入_04

现在问题来了,那如果我猫又想在草坪上唱歌了,狗也想去森林里唱歌了,有没有发现你的代码要改动很大?根据依赖倒置的说法,上层模块不应该依赖于下层木块,而是

共同依赖于一个抽象。抽象也不能依赖具象,而是具象依赖抽象。猫和狗的作用在这里都是唱歌,那么抽象出一个接口ISing,把森林和草坪抽象为场景(Scene),好,说改就改,改好的代码如下:

public interface  ISing
    {
        void Sing(Scene scene);
    }
    public class Cat: ISing
    {
        public  void Sing(Scene scene)
        {
            Console.WriteLine("I'm a Cat,I like Singing in the "+ scene.Space);
        }
    }
    public class Dog: ISing
    {
        public void Sing(Scene scene)
        {
            Console.WriteLine("I'm a Dog,I like Singing in the " + scene.Space);
        }
    }
    public abstract  class Scene
    {
        public string Space { get; set;}
    }

    public class Forest: Scene
    {
        public Forest()
        {
            base.Space = "Forest";
        }
    }
    public class Lawn: Scene
    {
        public Lawn()
        {
            base.Space = "Lawn";
        }
    }

    public class MainClass
    {
        public static void RunApp()
        {
            Scene scene = new Forest();
            ISing sing = new Dog();
            sing.Sing(scene);

            scene = new Lawn();
            sing = new Cat();
            sing.Sing(scene);
        }
    }

 

类依赖关系图如下:

依赖倒置的分层架构 DDD 代码github 依赖倒置和控制反转_依赖注入_05

我们目的是减少依赖,但看上去依赖更加复杂了,依赖还依赖接口以及抽象类。接口和抽象类是稳定的,我们可以不考虑,除了MainApp类对其他类的依赖外,其他的依赖都符合依赖倒置原则。那我们现在来解耦 MainApp类,修改代码如下:

public class MainClass
    {
        Scene scene; ISing sing;
        public MainClass(Scene scene, ISing sing)
        {
            this.scene = scene;
            this.sing = sing;
        }

        public void RunApp()
        {
            sing.Sing(scene);
        }
    }
MainClass main = new MainClass(new Lawn(),new Cat());
            main.RunApp();

 

类依赖关系简化为:

依赖倒置的分层架构 DDD 代码github 依赖倒置和控制反转_依赖关系_06

这便是通过依赖注入中的构造函数注入来解耦的。我们还可以利用反射继续解耦,更换不同的动物,不同的场景而不用修改代码

Assembly assembly = Assembly.Load(ConfigurationManager.AppSettings["AssemName"]);
            ISing sing = (ISing)assembly.CreateInstance(ConfigurationManager.AppSettings["AssemName"] + "." + ConfigurationManager.AppSettings["Animal"]);
            Scene scene = (Scene)assembly.CreateInstance(ConfigurationManager.AppSettings["AssemName"] + "." + ConfigurationManager.AppSettings["Scene"]);

            MainClass main = new MainClass(new Lawn(),new Cat());
            main.RunApp();
<appSettings>
    <add key="AssemName" value="ConsoleTest"/>
    <add key="Animal" value="Dog"/>
    <add key="Scene" value="Forest"/>
  </appSettings>

 

运行结果:

依赖倒置的分层架构 DDD 代码github 依赖倒置和控制反转_控制反转_07

这便是容器的部分功能的实现方式。

 

 

 

 

 

 

真正的大师永远怀着一颗学徒的心。