深度理解依赖注入(Dependence Injection) Inside ObjectBuilder Part1

前面的话:提到依赖注入,大家都会想到老马那篇经典的文章 。其实, 本文就是相当于对那篇文章的解读。所以,如果您对原文已经有了非常深刻的理解,完全不需要再看此文;但是,如果您和笔者一样,以前曾经看过,似乎看懂了, 但似乎又没抓到什么要领,不妨看看笔者这个解读,也许对您理解原文有一定帮助。

1.依赖在哪里
   老马举了一个小例子,是开发一个电影列举器(MovieList),这个电影列举器需要使用一个电影查找器(MovieFinder)提供的服务,伪码如 下:

 1 /* 服务的接口 */
 2 public   interface  MovieFinder  {
 3     ArrayList findAll();
 4 }

 5
 6 /* 服务的消费者 */
 7 class  MovieLister
 8 {
 9      public  Movie[] moviesDirectedBy(String arg)  {
10         List allMovies  =  finder.findAll();
11          for  (Iterator it  =  allMovies.iterator(); it.hasNext();)  {
12             Movie movie  =  (Movie) it.next();
13              if  ( ! movie.getDirector().equals(arg)) it.remove();
14         }

15          return  (Movie[]) allMovies.toArray( new  Movie[allMovies.size()]);
16     }

17
18      /* 消费者内部包含一个将指向具体服务类型的实体 对象 */
19      private  MovieFinder finder;
20      /* 消费者需要在某一个时刻去实例化具体的服务。 这是我们要解耦的关键所在,
21      *因为这样的处理方式造成了服务消费者和服务提供者的强耦合关系(这种耦合是在编译期就确定下来的)。
22      * */

23      public  MovieLister()  {
24         finder  =   new  ColonDelimitedMovieFinder( " movies1.txt " );
25     }

26 }


从上面代码的注释中可以看到,MovieLister和ColonDelimitedMovieFinder(这可以使任意一个实现了 MovieFinder接口的类型)之间存在强耦合关系,如下图所示:
图1
这使得MovieList很难作为一个成熟的组件去发布,因为在不同的应用环境中(包括同一套软件系统被不同用户使用的时候),它所要依赖的电影查找器可 能是千差万别的。所以,为了能实现真正的基于组件的开发,必须有一种机制能同时满足下面两个要求:
 (1)解除MovieList对具体MoveFinder类型的强依赖(编译期依赖)。
 (2)在运行的时候为MovieList提供正确的MovieFinder类型的实例。
   换句话说,就是在运行的时候才产生MovieList和 MovieFinder之间的依赖关系(把这种依赖关系在一个合适的时候“注入”运行时),这恐怕就是Dependency Injection这个术语的由来 。再换句话说,我们提到过解除强依赖,这并不是说MovieList和MovieFinder之间的依赖 关系不存在了,事实上MovieList无论如何也需要某类MovieFinder提供的服务,我们只是把这种依赖的建立时间推后了,从编译器推迟到运行 时了。
   依赖关系在OO程序中是广泛存在的,只要A类型中用到了B类型实例,A就依赖于B。前面笔者谈到的内容是把概念抽象到了服务使用者和服务提供者的角度,这 也符合现在SOA的设计思路。从另一种抽象方式上来看,可以把MovieList看成我们要构建的主系统,而MovieFinder是系统中的 plugin,主系统并不强依赖于任何一个插件,但一旦插件被加载,主系统就应该可以准确调用适当插件的功能。
   其实不管是面向服务的编程模式,还是基于插件的框架式编程,为了实现松耦合(服务调用者和提供者之间的or框架和插件之间的),都需要在必要的位置实现面向接口编程 ,在此基础之上,还应该有一种方便的机制实现具体类型之间的运行时绑定 ,这就是DI所要解决的问题。

2.DI的实现方式
   和上面的图1对应的是,如果我们的系统实现了依赖注入,组件间的依赖关系就变成了图2:
图2
说白了,就是要提供一个容器,由容器来完成(1)具体ServiceProvider的创建(2)ServiceUser和 ServiceProvider的运行时绑定。下面我们就依次来看一下三种典型的依赖注入方式的实现。特别要说明的是,要理解依赖注入的机制,关键是理解 容器的实现方式。本文后面给出的容器参考实现,均为黄忠成老师 的代码,笔者仅在其中 加上了一些关键注释而已。

2.1 Constructor Injection(构造器注入)
 我们可以看到,在整个依赖注入的数据结构中,涉及到的重要的类型就是ServiceUser, ServiceProvider和Assembler三者,而这里所说的构造器,指的是ServiceUser 的构造器 。也就是说,在构造ServiceUser实例的时候,才把真正的ServiceProvider传给他:

1 class  MovieLister
2 {
3     // 其他内容,省略
4
5     public  MovieLister(MovieFinder finder)
6     {
7         this .finder  =  finder;
8    }

9 }

接下来我们看看Assembler应该如何构建:

 1 private  MutablePicoContainer configureContainer()  {
 2     MutablePicoContainer pico  =   new  DefaultPicoContainer();
 3     
 4      // 下面就是把ServiceProvider和ServiceUser都放入容器的过 程,以后就由容器来提供ServiceUser的已完成依赖注入实例,
 5      // 其中用到的实例参数和类型参数一般是从配置档中读取的,这里是个简单的写法。
 6      // 所有的依赖注入方法都会有类似的容器初始化过程,本文在后面的小节中就不再重复这一段 代码了。
 7     Parameter[] finderParams  =    { new  ConstantParameter( " movies1.txt " )} ;
 8     pico.registerComponentImplementation(MovieFinder. class , ColonMovieFinder. class , finderParams);
 9     pico.registerComponentImplementation(MovieLister. class );
10      // 至此,容器里面装入了两个类型,其中没给出构造参数的那一个 (MovieLister)将依靠其在构造器中定义的传入参数类型,在容器中
11      // 进行查找,找到一个类型匹配项即可进行构造初始化。
12      return  pico;
13 }

需要在强调一下的是,依赖并未消失,只是延后到了容器被构建的时刻。所以正如图2中您已经看到的,容器本身(更准确的说,是一个容器运行实例的构建过程 )对ServiceUser和ServiceProvoder都是存在依赖关系的。 所以,在这样的体系结构里,ServiceUser、ServiceProvider和容器都是稳定的,互相之间也没有任何依赖关系;所有的依赖关系、所 有的变化都被封装进了容器实例的创建过程里,符合我们对服务应用的理解。而且,在实际开发中我们一般会采用配置文件来辅助容器实例的创建,将这种变化性排 斥到编译期之外。
   即使还没给出后面的代码,你也一定猜得到,这个container类一定有一个GetInstance(Type t)这样的方法,这个方法会为我们返回一个已经注入完毕的MovieLister。 一个简单的应用如下:

1 public   void  testWithPico() 
2 {
3     MutablePicoContainer pico  =  configureContainer();
4     MovieLister lister  =  (MovieLister) pico.getComponentInstance(MovieLister. class );
5     Movie[] movies  =  lister.moviesDirectedBy( " Sergio Leone " );
6     assertEquals( " Once Upon a Time in the West " , movies[ 0 ].getTitle());
7 }

上面最关键的就是对pico.getComponentInstance的调用。Assembler会在这个时候调用MovieLister的构造器,构 造器的参数就是当时通过pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams)设置进去的实际的ServiceProvider--ColonMovieFinder。下面请看这个容器的参考代码:

构造注入所需容器的伪码

 

2.2 Setter Injection(设值注入)
   这种注入方式和构造注入实在很类似,唯一的区别就是前者在构造函数的调用过程中进行注入,而它是通过给属性赋值来进行注入。无怪乎 PicoContainer和Spring都是同时支持这两种注入方式。Spring对通过XML进行配置有比较好的支持,也使得Spring中更常使用 设值注入的方式:

 1 < beans >
 2      < bean  id ="MovieLister"  class ="spring.MovieLister" >
 3          < property  name ="finder" >
 4              < ref  local ="MovieFinder" />
 5          </ property >
 6      </ bean >
 7      < bean  id ="MovieFinder"  class ="spring.ColonMovieFinder" >
 8          < property  name ="filename" >
 9              < value > movies1.txt </ value >
10          </ property >
11      </ bean >
12 </ beans >

下面也给出支持设值注入的容器参考实现,大家可以和构造器注入的容器对照起来看,里面的差别很小,主要的差别就在于,在获取对象实例 (GetInstance)的时候,前者是通过反射得到待创建类型的构造器信息,然后根据构造器传入参数的类型在容器中进行查找,并构造出合适的实例;而 后者是通过反射得到待创建类型的所有属性,然后根据属性的类型在容器中查找相应类型的实例。

设值注入的容器实现伪码

2.3 Interface Injection (接口注入)
   这是笔者认为最不够优雅的一种依赖注入方式。要实现接口注入,首先ServiceProvider要给出一个接口定义:

1 public   interface  InjectFinder  {
2      void  injectFinder(MovieFinder finder);
3 }

接下来,ServiceUser必须实现这个接口:

1 class  MovieLister: InjectFinder
2 {
3     public   void  injectFinder(MovieFinder finder)  {
4        this .finder  =  finder;
5     }

6 }

容器所要做的,就是根据接口定义调用其中的inject方法完成注入过程,这里就不在赘述了,总的原理和上面两种依赖注入模式没有太多区别。

2.4  除了DI,还有Service Locator
   上面提到的依赖注入只是消除ServiceUser和ServiceProvider之间的依赖关系的一种方法,还有另一种方法:服务定位器 (Service Locator)。也就是说,由ServiceLocator来专门负责提供具体的ServiceProvider。当然,这样的话 ServiceUser不仅要依赖于服务的接口,还依赖于ServiceContract。仍然是最早提到过的电影列举器的例子,如果使用Service Locator来解除依赖的话,整个依赖关系应当如下图所示:
图3
用起来也很简单,在一个适当的位置(比如在一组相关服务即将被调用之前)对ServiceLocator进行初始化,用到的时候就直接用 ServiceLocator返回ServiceProvider实例:

1 // 服务定位器的初始化
2 ServiceLocator locator  =   new  ServiceLocator();
3 locator.loadService( " MovieFinder " new  ColonMovieFinder( " movies1.txt " ));
4 ServiceLocator.load(locator);

5 // 服务定义器的使用
6 // 其实这个使用方式体现了服务定位器和依赖注入模式的最大差别:ServiceUser需 要显示的调用ServiceLocator,从而获取自己需要的服务对象;
7 // 而依赖注入则是隐式的由容器完成了这一切。
8 MovieFinder finder  =  (MovieFinder) ServiceLocator.getService( " MovieFinder " );
9

正因为上面提到过的ServiceUser对ServiceLocator的依赖性,从提高模块的独立性(比如说,你可能把你构造的 ServiceUser或者ServiceProvider给第三方使用)上来说,依赖注入可能更好一些,这恐怕也是为什么大多数的IOC框架都选用了 DI的原因。ServiceLocator最大的优点可能在于实现起来非常简单,如果您开发的应用没有复杂到需要采用一个IOC框架的程度,也许您可以试 着采用它。

3.广义的服务
   文中很多地方提到服务使用者(ServiceUser)和服务提供者(ServiceProvider)的概念,这里的“服务”是一种非常广义的概念,在 语法层面就是指最普通的依赖关系(类型A中有一个B类型的变量,则A依赖于B)。如果您把服务理解为WCF或者Web Service中的那种服务概念,您会发现上面所说的所有技术手段都是没有意义的。以WCF而论,其客户端和服务器端本就是依赖于Contract的松耦 合关系,其实这也从另一个角度说明了SOA应用的优势所在。

参考资料:
Object Builder Application Block

 

一、 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 實作體如 Avalo n 、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 Injectio n
 
  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 實作體,回傳後指定給 InputAccep t DataProcessBuilder.Build 函式所做的事也大致相同。這樣的設計是將原本位於主程式中 IDataProcessor 物件的建立動作,轉移到 DataProcessorFactory DataProcessorBuilder 上,這也算是一種 IoC 觀念的實現,只是這種轉移同時也將主程式與 IDataProcessor 物件間的關聯,平移成主程式與 DataProcessorFactory 間的關聯,當需要建立的物件一多時,問題又將回到原點,程式中一定會充斥著 AFactor y 、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 介面,此介面只有一個需要實作的函式: Creat e 。方便讀者易於理解這個設計概念,圖1以流程圖呈現這個設計的。
圖1
那這樣的設計有何優勢?很明顯的,這個設計已經將主程式與 DataProcessorFactory 關聯切除,轉移成主程式與 GenericFactory 的關聯,由於只使用一個 Factory GenericFactor y ,所以不存在於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 物件的行為。二是使用 Containe r( 容 器),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 函式,主程式也可以藉由 ServiceManage r ,解開與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 <string ,object > CreateConstructorParameter(Type targetType)
        {
            Dictionary <string , object > paramArray = new Dictionary <string , object >();
 
            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 <string , object > 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 來取得。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值