依赖倒置、控制反转、依赖注入、容器 这些概念的理解

 

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

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);
        }
    }
 MainClass.RunApp();
运行结果:
以上三个类的依赖关系图为:
如果小狗看见了,它想在草地上唱歌怎么办?我们是不是应该要增加俩个类 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);
        }
    }

 

运行结果
这几个类的依赖关系图:
现在问题来了,那如果我猫又想在草坪上唱歌了,狗也想去森林里唱歌了,有没有发现你的代码要改动很大?根据依赖倒置的说法,上层模块不应该依赖于下层木块,而是
共同依赖于一个抽象。抽象也不能依赖具象,而是具象依赖抽象。猫和狗的作用在这里都是唱歌,那么抽象出一个接口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);
        }
    }

 

类依赖关系图如下:
我们目的是减少依赖,但看上去依赖更加复杂了,依赖还依赖接口以及抽象类。接口和抽象类是稳定的,我们可以不考虑,除了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();

 

类依赖关系简化为:

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

 

运行结果:

这便是容器的部分功能的实现方式。
 
 
 
 
 
 
posted @ 2019-05-01 07:22  NCat  阅读(578)  评论(0)    收藏  举报