zoukankan      html  css  js  c++  java
  • IOC简介

    一、IoC 简介

    IoC的全名是『Inversion of Control』,字面上的意思是『控制反转』,要了解这个名词的真正含意,得从『控制』这个词切入。一般来说,当设计师撰写一个Console程序时,控制权是在该程序上,它决定着何时该印出讯息、何时又该接受使用者输入、何时该进行数据处理,如程序1。

    程序1

    using System; 
    using System.Collections.Generic; 
    using System.Text; 

    namespace ConsoleApplication2 

        class Program 
        { 
            static void Main(string[] args) 
            { 
                Console.Write("Please Input Some Words:"); 
                string inputData = Console.ReadLine(); 
                Console.WriteLine(inputData); 
                Console.Read(); 
            } 
        } 
    }

    从整个流程上看来,OS将控制权交给了此程序,接下来就看此程序何时将控制权交回,这是Console模式的标准处理流程。程序1演译了『控制』这个字的意思,那么『反转』这个词的含义呢?这可以用一个Windows Application来演示,如程序2。

    程序2

    using System; 
    using System.Collections.Generic; 
    using System.ComponentModel; 
    using System.Data; 
    using System.Drawing; 
    using System.Text; 
    using System.Windows.Forms; 
      
    namespace WindowsApplication10 

        public partial class Form1 : Form 
        { 
            public Form1() 
            { 
                InitializeComponent(); 
            } 
      
            private void button1_Click(object sender, EventArgs e) 
            { 
                MessageBox.Show(textBox1.Text); 
            } 
        } 
    }

    与程序1不同,当程序2被执行后,控制权其实并不在此程序中,而是在底层的Windows Forms Framework上,当此程序执行后,控制权会在Application.Run函数调用后,由主程序转移到Windows Forms Framework上,进入等待讯息的状态,当用户按下了Form上的按钮后,底层的Windows Forms Framework会收到一个讯息,接着会依照讯息来 调用button1_Click方法,此时控制权就由Windows Forms Framework转移到了主程序。程序2充份演译了『控制反转』的意含,也就是将原本位于主程序中的控制权,反转到了Windows Forms Framework上。

    二、Dependency Injection

    IoC的中心思想在于控制权的反转,这个概念于现今的Framework中相当常见,.NET Framework中就有许多这样的例子,问题是!既然这个概念已经 实现于许多Framework中,那为何近年来IoC会于社群引起这么多的讨论?著名的IoC实现对象如Avalon、Spring又达到了什么目的呢?就笔者的认知,IoC是一个广泛的概念,主要中心思想就在于控制权的反转,Windows Forms Framework与Spring在IoC的大概念下,都可以算是IoC的实现对象,两者不同之处在于究竟反转了那一部份的控制权,Windows Forms Framework将主程序的控制权反转到了自身上,Spring则是将对象的建立、释放、配置等控制权反转到自身,虽然两者都符合IoC的大概念,但设计初衷及欲达成的目的完全不同,因此用IoC来统称两者,就显得有些笼统及模糊。设计大师Martin Fowler针对Spring这类型IoC实现对象提出了一个新的名词『Dependency Injection』,字面上的意思是『依赖注入』。对笔者而言,这个名词比起IoC更能描述现今许多宣称支持IoC的Framework内部的行为,在Martin Fowler的解释中, Dependency Injection分成三种,一是Interface Injection(接口注射)、Constructor Injection(构造函数注射)、Setter Injection(设值注射)。

    2-1、Why we need Dependency Injection?

    OK,花了许多篇幅在解释IoC与Dependency Injection两个概念,希望读者们已经明白这两个名词的涵意,在切入Dependency Injection这个主题前,我们要先谈谈为何要使用Dependency Injection,及这样做带来了什么好处,先从程序3的例子开始。

    程序3

    using System; 
    using System.Collections.Generic; 
    using System.Text; 
      
    namespace DISimple 

        class Program 
        { 
            static void Main(string[] args) 
            { 
                InputAccept accept = new InputAccept(new PromptDataProcessor()); 
                accept.Execute(); 
                Console.ReadLine(); 
            } 
        } 
      
        public class InputAccept 
        { 
            private IDataProcessor _dataProcessor; 
      
            public void Execute() 
            { 
                Console.Write("Please Input some words:"); 
                string input = Console.ReadLine(); 
                input = _dataProcessor.ProcessData(input); 
                Console.WriteLine(input); 
            } 
      
            public InputAccept(IDataProcessor dataProcessor) 
            { 
                _dataProcessor = dataProcessor; 
            } 
        } 
      
        public interface IDataProcessor 
        { 
            string ProcessData(string input); 
        } 
      
        public class DummyDataProcessor : IDataProcessor 
        { 
      
            #region IDataProcessor Members 
      
            public string ProcessData(string input) 
            { 
                return input; 
            } 
      
            #endregion 
        } 
      
        public class PromptDataProcessor : IDataProcessor 
        { 
            #region IDataProcessor Members 
      
            public string ProcessData(string input) 
            { 
                return "your input is: " + input; 
            } 
      
            #endregion 
        } 
    }

    这是一个简单且无用的例子,但却可以告诉我们为何要使用Dependency Injection,在这个例子中,必须在建立InputAccept对象时传入一 个实现IDataProcessor接口的对象,这是Interface Base Programming概念的设计模式,这样做的目的是为了降低InputAccept与实现对象间的耦合关系,重用InputAccept的执行流程,以此来增加程序的延展性。那这个设计有何不当之处呢?没有!问题不在InputAccept、IDataProcessor的设计,而在于使用的方式。

    InputAccept accept = new InputAccept(new PromptDataProcessor());

    使用InputAccept时,必须在建立对象时传入一个实现IDataProcess接口的对象,此处直接建立一个PromptDataProcessor对象传入,这使得主程序与PromptDataProcessor对象产生了关联性,间接的摧毁使用IDataProcessor时所带来的低耦合性,那要如何解决这个问题呢?读过Design Patterns的读者会提出以Builder、Factory等样式解决这个问题,如下所示。

    //Factory 
    InputAccept accept = new InputAccept(DataProcessorFactory.Create()); 
    //Builder 
    InputAccept accept = new InputAccept(DataProcessorBulder.Build()); 

    两者的实际流程大致相同,DataProcessorFactory.Create方法会依据组态档的设定来建立指定的IDataProcessor实现对象,回传后指定给InputAccept,DataProcessBuilder.Build方法所做的事也大致相同。这样的设计是将原本位于主程序中IDataProcessor对象的建立动作,转移到DataProcessorFactory、DataProcessorBuilder上,这也算是一种IoC观念的实现,只是这种转移同时也将主程序与IDataProcessor对象间的关联,平移成主程序与DataProcessorFactory间的关联,当需要建立的对象一多时,问题又将回到原点,程序中一定会充斥着AFactory、BFactory等Factory对象。彻底将关联性降到最低的方法很简单,就是设计Factory的Factory、或是Builder的Builder,如下所示。

    //declare 
    public class DataProcessorFactory : IFactory
    .......... 
    //Builder 
    public class DataProcessorBuilder : IBuilder
    ........... 
    .................... 

    //initialize 
    //Factory  
    GenericFactory.RegisterTypeFactory(typeof(IDataProcessor),typeof(DataProcessorFactory)); 
    //Builder 
    GenericFactory.RegisterTypeBuilder(typeof(IDataProcessor),typeof(DataProcessorBuilder)); 
    ................ 

    //Factory 
    InputAccept accept = new InputAccept(GenericFactory.Create(typeof(IDataProcessor)); 
    //Builder 
    InputAccept accept = new InputAccept(GenericBuilder.Build(typeof(IDataProcessor)); 

    这个例子中,利用了一个GenericFactory对象来建立InputAccept所需的IDataProcessor对象,当GenericFactory.Create方法被 调用时,它会查询所拥有的Factory对象对应表,这个对应表是以type of base class/type of factory成对的格式存放,程序必须在一启动时准备好这个对应表,这可以透过组态档或是程序代码来完成,GenericFactory.Create方法在找到所传入的type of base class所对应的type of factory后,就建立该Factory的实体,然后调用该Factory对象的Create方法来建立IDataProcessor对象实体后回传。另外,为了统一Factory的 调用方式,GenericFactory要求所有注册的Factory对象必须实现IFactory接口,此接口只有一个需要实现的方法:Create。方便读者易于理解这个设计概念,图1以流程图呈现这个设计的。

    图1

    那这样的设计有何优势?很明显的,这个设计已经将主程序与DataProcessorFactory关联切除,转移成主程序与GenericFactory的关联,由于只使用一个Factory:GenericFactory,所以不存在于AFactory、BFactory这类问题。这样的设计概念确实降低了对象间的关联性,但仍然不够完善,因为有时对象的构造函数会需要一个以上的参数,但GenericFactory却未提供途径来传入这些参数(想象当InputAccept也是经由GenericFactory建立时),当然!我们可以运用object[]、params等途径来传入这些参数,只是这么做的后果是,主程序会与实体对象的构造函数产生关联,也就是间接的与实体对象产生关联。要切断这层关联,我们可以让GenericFactory自动完成InputAccept与IDataProcessor实体对象间的关联,也就是说在GenericFactory中,依据InputAccept的构造 函数声明,取得参数类型,然后使用该参数类型(此例就是IDataProcessor)来调用GenericFactory.Create方法建立实体的对象,再将这个对象传给InputAccept的构造函数,这样主程序就不会与InputAccept的构造函数产生关联,这就是Constructor Injection(构造函数注入)的概念。以上的讨论,我们可以理出几个重点,一、Dependency Injection是用来降低主程序与对象间的关联,二、Dependency Injection同时也能降低对象间的互联性,三、Dependency Injection可以简化对象的建立动作,进而让对象更容易使用,试想!只要调用GenericFactory.Create(typeof(InputAccept))跟原先的设计,那个更容易使用?不过要拥有这些优点,我们得先拥有着一个完善的架构,这就是ObjectBuilder、Spring、Avalon等Framework出现的原因。

    PS:这一小节进度超前许多,接下来将回归Dependency Injection的三种模式,请注意!接下来几小节的讨论是依据三种模式的精神,所以例子以简单易懂为主,不考虑本文所提及的完整架构。

    2-2、Interface Injection

    Interface Injection指的是将原本建构于对象间的依赖关系,转移到一个接口上,程序4是一个简单的例子。

    程序4

    using System; 
    using System.Collections.Generic; 
    using System.Text; 
      
    namespace ConsoleApplication2 

        class Program 
        { 
            static void Main(string[] args) 
            { 
                InputAccept accept = new InputAccept(); 
                accept.Inject(new DummyDataProcessor()); 
                accept.Execute(); 
                Console.Read(); 
            } 
        } 
      
        public class InputAccept 
        { 
            private IDataProcessor _dataProcessor; 
      
            public void Inject(IDataProcessor dataProcessor) 
            { 
                _dataProcessor = dataProcessor; 
            } 
      
            public void Execute() 
            { 
                Console.Write("Please Input some words:"); 
                string input = Console.ReadLine(); 
                input = _dataProcessor.ProcessData(input); 
                Console.WriteLine(input); 
            } 
        } 
      
        public interface IDataProcessor 
        { 
            string ProcessData(string input); 
        } 
      
        public class DummyDataProcessor : IDataProcessor 
        { 
      
            #region IDataProcessor Members 
      
            public string ProcessData(string input) 
            { 
                return input; 
            } 
      
            #endregion 
        } 
      
        public class PromptDataProcessor : IDataProcessor 
        { 
            #region IDataProcessor Members 
      
            public string ProcessData(string input) 
            { 
                return "your input is: " + input; 
            } 
      
            #endregion 
        } 
    }

    InputAccept对象将一部份的动作转移到另一个对象上,虽说如此,但InputAccept与该对象并未建立依赖关系,而是将依赖关系建立在一个接口:IDataProcessor上,经由一个方法传入实体对象,我们将这种应用称为Interface Injection。当然,如你所见,程序4的手法在实务应用上并未带来太多的好处,原因是执行Interface Injection动作的仍然是主程序,这意味着与主程序与该对象间的依赖关系仍然存在,要将Interface Injection的概念发挥到极致的方式有两个,一是使用组态文件,让主程序由组态文件中读入DummaryDataProcessor或是PromptDataProcessor,这样一来,主程序便可以在不重新编译的情况下,改变InputAccept对象的行为。二是使用Container(容器),Avalon是一个标准的范例。

    程序5

    public class InputAccept implements Serviceable { 
     private IDataProcessor m_dataProcessor; 
      
     public void service(ServiceManager sm) throws ServiceException { 
          m_dataProcessor = (IDataProcessor) sm.lookup("DataProcessor"); 
     } 
      
     public void Execute() { 
        ........ 
        string input = m_dataProcessor.ProcessData(input); 
        ........ 
     } 
    }

    在Avalon的模式中,ServiceManager扮演着一个容器,设计者可以透过程序或组态文件,将特定的对象,如DummyDataProcessor推到容器中,接下来InputAccept就只需要询问容器来取得对象即可,在这种模式下,InputAccept不需再撰写Inject方法,主程序也可以藉由ServiceManager,解开与DummyDataProcessor的依赖关系。使用Container时有一个特质,就是Injection动作是由Conatiner来自动完成的,这是Dependency Injection的重点之一。

    PS:在正确的Interface Injection定义中,组装InputAccept与IDataProcessor的是容器,在本例中,我并未使用容器,而是提取其行为。

    2-3、Constructor Injection

    Constructor Injection意指构造函数注入,主要是利用构造函数参数来注入依赖关系,构造函数注入通常是与容器紧密相关的,容器允许设计者透过特定方法,将欲注入的对象事先放入容器中,当使用端要求一个支持构造函数注入的对象时,容器中会依据目标对象的构造函数参数,一一将已放入容器中的对象注入。程序6是一个简单的容器类别,其支持Constructor Injection。

    程序6

    public static class Container 

        private static Dictionary<Type, object> _stores = null

        private static Dictionary<Type, object> Stores 
        { 
            get 
            { 
                if (_stores == null
                    _stores = new Dictionary<Type, object>(); 
                return _stores; 
            } 
        } 

        private static Dictionary<stringobject> CreateConstructorParameter(Type targetType) 
        { 
            Dictionary<stringobject> paramArray = new Dictionary<stringobject>(); 

            ConstructorInfo[] cis = targetType.GetConstructors(); 
            if (cis.Length > 1) 
                throw new Exception("target object has more then one constructor,container can't peek one for you."); 

            foreach (ParameterInfo pi in cis[0].GetParameters()) 
            { 
                if (Stores.ContainsKey(pi.ParameterType)) 
                    paramArray.Add(pi.Name, GetInstance(pi.ParameterType)); 
            } 
            return paramArray; 
        } 

        public static object GetInstance(Type t) 
        { 
            if (Stores.ContainsKey(t)) 
            { 
                ConstructorInfo[] cis = t.GetConstructors(); 
                if (cis.Length != 0) 
                { 
                    Dictionary<stringobject> paramArray = CreateConstructorParameter(t); 
                    List<object> cArray = new List<object>(); 
                    foreach (ParameterInfo pi in cis[0].GetParameters()) 
                    { 
                        if (paramArray.ContainsKey(pi.Name)) 
                            cArray.Add(paramArray[pi.Name]); 
                        else 
                            cArray.Add(null); 
                    } 
                    return cis[0].Invoke(cArray.ToArray()); 
                } 
                else if (Stores[t] != null
                    return Stores[t]; 
                else 
                    return Activator.CreateInstance(t, false); 
            } 
            return Activator.CreateInstance(t, false); 
        } 

        public static void RegisterImplement(Type t, object impl) 
        { 
            if (Stores.ContainsKey(t)) 
                Stores[t] = impl; 
            else 
                Stores.Add(t, impl); 
        } 

        public static void RegisterImplement(Type t) 
        { 
            if (!Stores.ContainsKey(t)) 
                Stores.Add(t, null); 
        } 
    }

    Container类别提供了两个方法,RegisterImplement有两个重载方法,一接受一个Type对象及一个不具型物件,它会将传入的Type及对象成对的放入Stores这个Collection中,另一个重载方法则只接受一个Type对象,调用这个方法代表调用端不预先建立该对象,交由GetInstance方法来建立。GetInstance方法负责建立对象,当要求的对象类型存在于Stores记录中时,其会取得该类型的构造函数,并依据构造函数的参数,一一调用GetInstance方法来建立对象。程序7是使用这个Container的范例。

    程序7

    class Program 

        static void Main(string[] args) 
        { 
            Container.RegisterImplement(typeof(InputAccept)); 
            Container.RegisterImplement(typeof(IDataProcessor), new PromptDataProcessor()); 
            InputAccept accept = (InputAccept)Container.GetInstance(typeof(InputAccept)); 
            accept.Execute(); 
            Console.Read(); 
        } 


    public class InputAccept 

        private IDataProcessor _dataProcessor; 

        public void Execute() 
        { 
            Console.Write("Please Input some words:"); 
            string input = Console.ReadLine(); 
            input = _dataProcessor.ProcessData(input); 
            Console.WriteLine(input); 
        } 

        public InputAccept(IDataProcessor dataProcessor) 
        { 
            _dataProcessor = dataProcessor; 
        } 


    public interface IDataProcessor 

        string ProcessData(string input); 


    public class DummyDataProcessor : IDataProcessor 

        #region IDataProcessor Members 

        public string ProcessData(string input) 
        { 
            return input; 
        } 

        #endregion 


    public class PromptDataProcessor : IDataProcessor 

        #region IDataProcessor Members 

        public string ProcessData(string input) 
        { 
            return "your input is: " + input; 
        } 

        #endregion 
    }

    2-4、Setter Injection

    Setter Injection意指设值注入,主要概念是透过属性的途径,将依赖对象注入目标对象中,与Constructor Injection模式一样,这个模式同样需要容器的支持,程序8是支持Setter Injection的Container程序行表。

    程序8

    public static class Container 

        private static Dictionary<Type, object> _stores = null

        private static Dictionary<Type, object> Stores 
        { 
            get 
            { 
                if (_stores == null
                    _stores = new Dictionary<Type, object>(); 
                return _stores; 
            } 
        } 

        public static object GetInstance(Type t) 
        { 
            if (Stores.ContainsKey(t)) 
            { 
                if (Stores[t] == null
                { 
                    object target = Activator.CreateInstance(t, false); 
                    foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(target)) 
                    { 
                        if (Stores.ContainsKey(pd.PropertyType)) 
                            pd.SetValue(target, GetInstance(pd.PropertyType)); 
                    } 
                    return target; 
                } 
                else 
                    return Stores[t]; 
            } 
            return Activator.CreateInstance(t, false); 
        } 

        public static void RegisterImplement(Type t, object impl) 
        { 
            if (Stores.ContainsKey(t)) 
                Stores[t] = impl; 
            else 
                Stores.Add(t, impl); 
        } 

        public static void RegisterImplement(Type t) 
        { 
            if (!Stores.ContainsKey(t)) 
                Stores.Add(t, null); 
        } 
    }

    程序代码与Constructor Injection模式大致相同,两者差异之处仅在于Constructor Injection是使用构造函数来注入,Setter Injection是使用属性来注入,程序9是使用此Container的范例。

    程序9

    class Program 

        static void Main(string[] args) 
        { 
            Container.RegisterImplement(typeof(InputAccept)); 
            Container.RegisterImplement(typeof(IDataProcessor), new PromptDataProcessor()); 
            InputAccept accept = (InputAccept)Container.GetInstance(typeof(InputAccept)); 
            accept.Execute(); 
            Console.Read(); 
        } 


    public class InputAccept 

        private IDataProcessor _dataProcessor; 

        public IDataProcessor DataProcessor 
        { 
            get 
            { 
                return _dataProcessor; 
            } 
            set 
            { 
                _dataProcessor = value
            } 
        } 

        public void Execute() 
        { 
            Console.Write("Please Input some words:"); 
            string input = Console.ReadLine(); 
            input = _dataProcessor.ProcessData(input); 
            Console.WriteLine(input); 
        } 
    }

    2-5、Service Locator

    在Martain Fowler的文章中,Dependency Injection并不是唯一可以将对象依赖关系降低的方式,另一种Service Locator架构也可以达到同样的效果,从架构角度来看,Service Locator是一个服务中心,设计者预先将Servcie对象推入Locator容器中,在这个容器内,Service是以Key/Value方式存在。欲使用该Service对象的对象,必须将依赖关系建立在Service Locator上,也就是说,不是透过构造函数、属性、或是方法来取得依赖对象,而是透过Service Locator来取得。

    本文转自:http://www.cnblogs.com/zhenyulu/articles/641728.html
    E文地址:http://www.martinfowler.com/articles/injection.html

     
         本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    LeetCode 40. 组合总和 II(Combination Sum II)
    LeetCode 129. 求根到叶子节点数字之和(Sum Root to Leaf Numbers)
    LeetCode 60. 第k个排列(Permutation Sequence)
    LeetCode 47. 全排列 II(Permutations II)
    LeetCode 46. 全排列(Permutations)
    LeetCode 93. 复原IP地址(Restore IP Addresses)
    LeetCode 98. 验证二叉搜索树(Validate Binary Search Tree)
    LeetCode 59. 螺旋矩阵 II(Spiral Matrix II)
    一重指针和二重指针
    指针的意义
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2428717.html
Copyright © 2011-2022 走看看