博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Inside ObjectBuilder Part1
阅读量:4189 次
发布时间:2019-05-26

本文共 13887 字,大约阅读时间需要 46 分钟。

 
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
實作體如
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
來取得。

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1282139

你可能感兴趣的文章
Observer模式
查看>>
高性能服务器设计
查看>>
图文介绍openLDAP在windows上的安装配置
查看>>
Pentaho BI开源报表系统
查看>>
Pentaho 开发: 在eclipse中构建Pentaho BI Server工程
查看>>
android中使用TextView来显示某个网址的内容,使用<ScrollView>来生成下拉列表框
查看>>
andorid里关于wifi的分析
查看>>
Hibernate和IBatis对比
查看>>
Spring MVC 教程,快速入门,深入分析
查看>>
Ubuntu Navicat for MySQL安装以及破解方案
查看>>
在C++中如何实现模板函数的外部调用
查看>>
HTML5学习之——HTML 5 应用程序缓存
查看>>
HTML5学习之——HTML 5 服务器发送事件
查看>>
mysql中用命令行复制表结构的方法
查看>>
hbase shell出现ERROR: org.apache.hadoop.hbase.ipc.ServerNotRunningYetException
查看>>
解决Rhythmbox乱码
查看>>
豆瓣爱问共享资料插件发布啦
查看>>
kermit的安装和配置
查看>>
linux中cat命令使用详解
查看>>
java中的异常机制
查看>>