• 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

     
         本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    小小的蜗牛有大大的梦想
    Spring整合的quartz任务调度的实现方式
    HDU/HDOJ 2612 Find a way 双向BFS
    在静态库中,实现自动的初始化与卸载接口
    CF 316C2(Tidying Up-二分图最大边权)
    Qt线程同步操作用QWaitCondition QMutex
    MQ、JMS以及ActiveMQ
    微博分享利器
    discuz清空session,导致session保存机制失败,session无法更新与解决
    路由器和交换机的综合实验(1)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2428717.html
Copyright © 2020-2023  润新知