zoukankan      html  css  js  c++  java
  • C# 依赖注入

    一、什么是依赖注入

    依赖注入的正式定义:

    依赖注入(Dependency Injection),是这样一个过程:由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。

    二、依赖注入的类别

    1.Setter注入
    Setter注入(Setter Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并设置一个Set方法作为注入点,这个Set方法接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。

    下面给出Setter注入的示例代码。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace SetterInjection
    {
        internal interface IServiceClass
        {
            String ServiceInfo();
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace SetterInjection
    {
        internal class ServiceClassA : IServiceClass
        {
            public String ServiceInfo()
            {
                return "我是ServceClassA";
            }
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace SetterInjection
    {
        internal class ServiceClassB : IServiceClass
        {
            public String ServiceInfo()
            {
                return "我是ServceClassB";
            }
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace SetterInjection
    {
        internal class ClientClass
        {
        //注入点
            private IServiceClass _serviceImpl;
        //客户类中的方法,初始化注入点  
            public void Set_ServiceImpl(IServiceClass serviceImpl)
            {
                this._serviceImpl = serviceImpl;
            }
      
            public void ShowInfo()
            {
                Console.WriteLine(_serviceImpl.ServiceInfo());
            }
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace SetterInjection
    {
        class Program
        {
            static void Main(string[] args)
            {
                IServiceClass serviceA = new ServiceClassA();
                IServiceClass serviceB = new ServiceClassB();
                ClientClass client = new ClientClass();
      
                client.Set_ServiceImpl(serviceA);
                client.ShowInfo();//结果:我是ServceClassA
                client.Set_ServiceImpl(serviceB);
                client.ShowInfo();//结果:我是ServceClassB
    
                Console.ReadLine();
            }
        }
    }
    View Code

    运行结果如下:

    2.构造注入

    另外一种依赖注入方式,是通过客户类的构造函数,向客户类注入服务类实例。

    构造注入(Constructor Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并以构造函数为注入点,这个构造函数接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。

    与Setter注入很类似,只是注入点由Setter方法变成了构造方法。这里要注意,由于构造注入只能在实例化客户类时注入一次,所以一点注入,程序运行期间是没法改变一个客户类对象内的服务类实例的。

    由于构造注入和Setter注入的IServiceClass,ServiceClassA和ServiceClassB是一样的,所以这里给出另外ClientClass类的示例代码。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace ConstructorInjection
    {
        internal class ClientClass
        {
            private IServiceClass _serviceImpl;
      
            public ClientClass(IServiceClass serviceImpl)
            {
                this._serviceImpl = serviceImpl;
            }
      
            public void ShowInfo()
            {
                Console.WriteLine(_serviceImpl.ServiceInfo());
            }
        }
    }
    View Code

    可以看到,唯一的变化就是构造函数取代了Set_ServiceImpl方法,成为了注入点。

    3. 依赖获取

    上面提到的注入方式,都是客户类被动接受所依赖的服务类,这也符合“注入”这个词。不过还有一种方法,可以和依赖注入达到相同的目的,就是依赖获取。

    依赖获取(Dependency Locate)是指在系统中提供一个获取点,客户类仍然依赖服务类的接口。当客户类需要服务类时,从获取点主动取得指定的服务类,具体的服务类类型由获取点的配置决定。

    可以看到,这种方法变被动为主动,使得客户类在需要时主动获取服务类,而将多态性的实现封装到获取点里面。获取点可以有很多种实现,也许最容易想到的就是建立一个Simple Factory作为获取点,客户类传入一个指定字符串,以获取相应服务类实例。如果所依赖的服务类是一系列类,那么依赖获取一般利用Abstract Factory模式构建获取点,然后,将服务类多态性转移到工厂的多态性上,而工厂的类型依赖一个外部配置,如XML文件。

    不过,不论使用Simple Factory还是Abstract Factory,都避免不了判断服务类类型或工厂类型,这样系统中总要有一个地方存在不符合OCP的if…else或switch…case结构,这种缺陷是Simple Factory和Abstract Factory以及依赖获取本身无法消除的,而在某些支持反射的语言中(如C#),通过将反射机制的引入彻底解决了这个问题(后面讨论)。

    下面给一个具体的例子,现在我们假设有个程序,既可以使用Windows风格外观,又可以使用Mac风格外观,而内部业务是一样的。

    上图乍看有点复杂,不过如果读者熟悉Abstract Factory模式,应该能很容易看懂,这就是Abstract Factory在实际中的一个应用。这里的Factory Container作为获取点,是一个静态类,它的“Type构造函数”依据外部的XML配置文件,决定实例化哪个工厂。下面还是来看示例代码。由于不同组件的代码是相似的,这里只给出Button组件的示例代码,完整代码请参考文末附上的完整源程序。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace DependencyLocate
    {
        internal interface IButton
        {
            String ShowInfo();
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace DependencyLocate
    {
        internal sealed class WindowsButton : IButton
        {
            public String Description { get; private set; }
      
            public WindowsButton()
            {
                this.Description = "Windows风格按钮";
            }
      
            public String ShowInfo()
            {
                return this.Description;
            }
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace DependencyLocate
    {
        internal sealed class MacButton : IButton
        {
            public String Description { get; private set; }
      
            public MacButton()
            {
                this.Description = " Mac风格按钮";
            }
      
            public String ShowInfo()
            {
                return this.Description;
            }
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace DependencyLocate
    {
        internal interface IFactory
        {
            IWindow MakeWindow();
      
            IButton MakeButton();
      
            ITextBox MakeTextBox();
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace DependencyLocate
    {
        internal sealed class WindowsFactory : IFactory
        {
            public IWindow MakeWindow()
            {
                return new WindowsWindow();
            }
      
            public IButton MakeButton()
            {
                return new WindowsButton();
            }
      
            public ITextBox MakeTextBox()
            {
                return new WindowsTextBox();
            }
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace DependencyLocate
    {
        internal sealed class MacFactory : IFactory
        {
            public IWindow MakeWindow()
            {
                return new MacWindow();
            }
      
            public IButton MakeButton()
            {
                return new MacButton();
            }
      
            public ITextBox MakeTextBox()
            {
                return new MacTextBox();
            }
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml;
      
    namespace DependencyLocate
    {
        internal static class FactoryContainer
        {
            public static IFactory factory { get; private set; }
      
            static FactoryContainer()
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load("http://www.cnblogs.com/Config.xml");
                XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0];
      
                if ("Windows" == xmlNode.Value)
                {
                    factory = new WindowsFactory();
                }
                else if ("Mac" == xmlNode.Value)
                {
                    factory = new MacFactory();
                }
                else
                {
                    throw new Exception("Factory Init Error");
                }
            }
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
      
    namespace DependencyLocate
    {
        class Program
        {
            static void Main(string[] args)
            {
                IFactory factory = FactoryContainer.factory;
                IWindow window = factory.MakeWindow();
                Console.WriteLine("创建 " + window.ShowInfo());
                IButton button = factory.MakeButton();
                Console.WriteLine("创建 " + button.ShowInfo());
                ITextBox textBox = factory.MakeTextBox();
                Console.WriteLine("创建 " + textBox.ShowInfo());
      
                Console.ReadLine();
            }
        }
    }
    View Code

    这里我们用XML作为配置文件。配置文件Config.xml如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <config>
        <factory>Mac</factory>
    </config>
    View Code

    可以看到,这里我们将配置设置为Mac风格,编译运行上述代码,运行结果如下:

    配置Mac风格后的运行结果

    现在,我们不动程序,仅仅将配置文件中的“Mac”改为Windows,运行后结果如下:

    配置为Windows风格后的运行结果

    从运行结果看出,我们仅仅通过修改配置文件,就改变了整个程序的行为(我们甚至没有重新编译程序),这就是多态性的威力,也是依赖注入效果。

    反射与依赖注入

    回想上面Dependency Locate的例子,我们虽然使用了多态性和Abstract Factory,但对OCP贯彻的不够彻底。在理解这点前,朋友们一定要注意潜在扩展在哪里,潜在会出现扩展的地方是“新的组件系列”而不是“组件种类”,也就是说,这里我们假设组件就三种,不会增加新的组件,但可能出现新的外观系列,如需要加一套Ubuntu风格的组件,我们可以新增UbuntuWindow、UbuntuButton、UbuntuTextBox和UbuntuFactory,并分别实现相应接口,这是符合OCP的,因为这是扩展。但我们除了修改配置文件,还要无可避免的修改FactoryContainer,需要加一个分支条件,这个地方破坏了OCP。依赖注入本身是没有能力解决这个问题的,但如果语言支持反射机制(Reflection),则这个问题就迎刃而解。

    我们想想,现在的难点是出在这里:对象最终还是要通过“new”来实例化,而“new”只能实例化当前已有的类,如果未来有新类添加进来,必须修改代码。如果,我们能有一种方法,不是通过“new”,而是通过类的名字来实例化对象,那么我们只要将类的名字作为配置项,就可以实现在不修改代码的情况下,加载未来才出现的类。所以,反射给了语言“预见未来”的能力,使得多态性和依赖注入的威力大增。

    下面是引入反射机制后,对上面例子的改进:

    可以看出,引入反射机制后,结构简单了很多,一个反射工厂代替了以前的一堆工厂,Factory Container也不需要了。而且以后有新组件系列加入时,反射工厂是不用改变的,只需改变配置文件就可以完成。下面给出反射工厂和配置文件的代码。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection;
    using System.Xml;
      
    namespace DependencyLocate
    {
        internal static class ReflectionFactory
        {
            private static String _windowType;
            private static String _buttonType;
            private static String _textBoxType;
      
            static ReflectionFactory()
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load("http://www.cnblogs.com/Config.xml");
                XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0];
      
                _windowType = xmlNode.ChildNodes[0].Value;
                _buttonType = xmlNode.ChildNodes[1].Value;
                _textBoxType = xmlNode.ChildNodes[2].Value;
            }
      
            public static IWindow MakeWindow()
            {
                return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _windowType) as IWindow;
            }
      
            public static IButton MakeButton()
            {
                return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _buttonType) as IButton;
            }
      
            public static ITextBox MakeTextBox()
            {
                return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _textBoxType) as ITextBox;
            }
        }
    }
    View Code

    配置文件如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <config>
        <window>MacWindow</window>
        <button>MacButton</button>
        <textBox>MacTextBox</textBox>
    </config>
    View Code

    反射不仅可以与Dependency Locate结合,也可以与Setter Injection与Construtor Injection结合。反射机制的引入,降低了依赖注入结构的复杂度,使得依赖注入彻底符合OCP,并为通用依赖注入框架(如Spring.NET中的IoC部分、Unity等)的设计提供了可能性。

  • 相关阅读:
    Python全栈开发——装饰器
    Python全栈开发——类
    Python全栈开发——Json & pickle & shelve & sys模块
    Python全栈开发——正则表达式
    实验3- 熟悉常用的 HBase 操作
    Hbase PleaseHoldException错误
    HDFS 中文件操作的错误集锦
    ubuntu在虚拟机下的安装 ~~~ Hadoop的安装及配置 ~~~ Hdfs中eclipse的安装
    假期周进度报告---08
    假期周进度报告---07
  • 原文地址:https://www.cnblogs.com/WarBlog/p/8118265.html
Copyright © 2011-2022 走看看