黄忠成

風雪之閣 - i live,so i writing

用户操作
[即时聊天] [发私信] [加为好友]
黃忠成ID:Code6421
159920次访问,排名478好友0人,关注者26
coding
Code6421的文章
原创 97 篇
翻译 0 篇
转载 0 篇
评论 221 篇
Code6421的公告
關於我:

黃忠成

  • 資深.NET 技術顧問
  • Run! PC 雜誌專欄作者
  • 程序員雜誌文章作者
  • PC Magazine 雜誌專欄作者
  • MSDN 專欄作者
  • MSDN 特約專屬講師
  • Microsoft .NET專屬講師
  • 台灣微軟最有價值專家(MVP 2008)
  • 台灣微軟特約技術顧問


  • 批評,指教,鼓勵, 請 寫信給我
    轉載文章請使用連結模式,
    請勿整篇Copy! 謝謝!


    我所提供的教育訓練:

    Windows Forms
    ASP.NET 2.0
    如有課程需要,請與我聯絡!

  • 我的著作:

  • 最近评论
    chenyong365:我一定会买一本.
    programlin:>>瀏覽器與Office有著同樣的命運,就是最後都會變成一個載體,趨近於OS的地位

    果然是英雄所見略同.
    上次看到一篇文章,大致上的內容是Microsoft正在積極規劃下一代的作業系統,不是Windows,而是一個完全跳脫出Windows概念的新OS.
    我猜想最近這幾年Microsoft推.Net,SOA架構與Windows Live都是……
    programlin:>>瀏覽器與Office有著同樣的命運,就是最後都會變成一個載體,趨近於OS的地位

    果然是英雄所見略同.
    上次看到一篇文章,大致上的內容是Microsoft正在積極規劃下一代的作業系統,不是Windows,而是一個完全跳脫出Windows概念的新OS.
    我猜想最近這幾年Microsoft推.Net,SOA架構與Windows Live都是……
    programlin:>>瀏覽器與Office有著同樣的命運,就是最後都會變成一個載體,趨近於OS的地位

    果然是英雄所見略同.
    上次看到一篇文章,大致上的內容是Microsoft正在積極規劃下一代的作業系統,不是Windows,而是一個完全跳脫出Windows概念的新OS.
    我猜想最近這幾年Microsoft推.Net,SOA架構與Windows Live都是……
    Code6421:to gutes,

    呃..我也不知道怎麼辦....我書的出版權是在金禾,
    不過金禾.....你也知道...Orz
    文章分类
    收藏
      相册
      风雪之阁
      朋友
      LOLOTA
      Moli
      Will 保哥
      匡正
      小朱
      董大偉
      賴榮樞
      存档
      订阅我的博客
      XML聚合  FeedSky
      订阅到鲜果
      订阅到Google
      订阅到抓虾
      订阅到BlogLines
      订阅到Yahoo
      订阅到GouGou
      订阅到飞鸽
      订阅到Rojo
      订阅到newsgator
      订阅到netvibes

      原创 Inside ObjectBuilder Part1收藏

      新一篇: Inside ObjectBuilder Part 2 | 旧一篇: Inside ASP.NET 2.0 – Controls Model

       
      Object Builder Application Block
       
      /黃忠成   
      2006/9/21
       
       
      一、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更能描述現今許多宣稱支援IoCFramework內部的行為,在Martin Fowler的解釋中, Dependency Injection分成三種,一是Interface Injection(介面注射)Constructor Injection(建構子注射)Setter Injection(設值注射)
       
      2-1Why we need Dependency Injection
       
       OK,花了許多篇幅在解釋IoCDependency 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的執行流程,以此來增加程式的延展性。那這個設計有何不當之處呢?沒有!問題不在InputAcceptIDataProcessor的設計,而在於使用的方式。
      InputAccept accept = new InputAccept(new PromptDataProcessor());
      使用InputAccept時,必須在建立物件時傳入一個實作IDataProcess介面的物件,此處直接建立一個PromptDataProcessor物件傳入,這使得主程式與PromptDataProcessor物件產生了關聯性,間接的摧毀使用IDataProcessor時所帶來的低耦合性,那要如何解決這個問題呢?讀過Design Patterns的讀者會提出以BuilderFactory等樣式解決這個問題,如下所示。
      //Factory
      InputAccept accept = new InputAccept(DataProcessorFactory.Create());
      //Builder
      InputAccept accept = new InputAccept(DataProcessorBulder.Build());
      兩者的實際流程大致相同,DataProcessorFactory.Create函式會依據組態檔的設定來建立指定的IDataProcessor實作體,回傳後指定給InputAcceptDataProcessBuilder.Build函式所做的事也大致相同。這樣的設計是將原本位於主程式中IDataProcessor物件的建立動作,轉移到DataProcessorFactoryDataProcessorBuilder上,這也算是一種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的關聯,由於只使用一個FactoryGenericFactory,所以不存在於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-2Interface 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-3Constructor 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-4Setter Injection
       
       Setter Injection意指設值注入,主要概念是透過屬性的途徑,將依賴物件注入目標物件中,與Constructor Injection模式一樣,這個模式同樣需要容器的支援,程式8是支援Setter InjectionContainer程式列表。
      程式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-5Service Locator
       
       Martain Fowler的文章中,Dependency Injection並不是唯一可以將物件依賴關係降低的方式,另一種Service Locator架構也可以達到同樣的效果,從架構角度來看,Service Locator是一個服務中心,設計者預先將Servcie物件推入Locator容器中,在這個容器內,Service是以Key/Value方式存在。欲使用該Service物件的物件,必須將依賴關係建立在Service Locator上,也就是說,不是透過建構子、屬性、或是方法來取得依賴物件,而是透過Service Locator來取得。

      发表于 @ 2006年09月25日 20:37:00|评论(loading...)|编辑

      新一篇: Inside ObjectBuilder Part 2 | 旧一篇: Inside ASP.NET 2.0 – Controls Model

      评论

      #zhongwanli 发表于2007-02-12 11:29:06  IP: 59.37.44.*
      M$的ServiceContainer对象就是Martin所说的Service Locator这种模式。
      发表评论  


      登录
      Csdn Blog version 3.1a
      Copyright © Code6421