Inside ObjectBuilder Part1

news/2024/7/3 12:16:58
 
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 來取得。




http://www.niftyadmin.cn/n/3658122.html

相关文章

ESRGAN阅读笔记

从三个地方入手修改了SRGAN&#xff1a; 网络结构adversarial lossperceptual loss 网络结构主要是引入了RRDB的结构&#xff0c;并且去掉了BN层&#xff0c;使用residual scaling和smaller initialization&#xff0c;就是residual在加的时候乘一个小于1的系数&#xff0c;在w…

Inside ObjectBuilder Part 2

Object Builder Application Block文/黃忠成2006/9/21 三、ObjectBuilder Application BlockObjectBuilder一開始出現於Microsoft所提出的Composite UI Application Block&#xff0c;主司物件的建立及釋放工作&#xff0c;她實現了本文前面所提及的Dependency Injection概念&a…

嵌入式系统学习笔记(1)——概述、发展历史

TTL电平高电平为5V低电平为0V&#xff0c;计算机串口使用RS232电平&#xff0c;高电平为-12V低电平为12V&#xff0c;所以使用TTL电平的单片机和计算机通讯时需要通过电平转换芯片max23251单片机是对8051单片机衍生系列的统称&#xff0c;STC是其中一种&#xff0c;STC是公司的…

Inside ObjectBuilder Part 4

ObjectBuilderApplicationBlock文/黄忠成2006/9/21八、ObjectBuilder实务8-1、ObjectBuilderWithConfiguration截至目前为止&#xff0c;本文一直以程序方式来配置ObjectBuidler建立对象所需的各种对象&#xff0c;但在实务上&#xff0c;这些动作应该是交由配置文件来负责&…

Towards Precise Supervision of Feature Super-Resolution for Small Object Detection 阅读笔记

Better to Follow, Follow to Be Better: Towards Precise Supervision of Feature Super-Resolution for Small Object Detection 文章针对2阶段的detection提出了一种对小面积ROI的feature进行SR的方法&#xff0c;并提出了对该SR进行监督的机制首先是发现问题&#xff0c;问…

Inside ObjectBuilder 范例

Object Builder Application Block文/黃忠成2006/9/21一、IoC 简介IoC 的全名是“Inversion of Control”&#xff0c;字面上的意思是“控制反转”&#xff0c;要了解这个名词的真正含义&#xff0c;得从“控制”这个词切入。一般來说&#xff0c;当程序员撰写一个Console 程序…

数电学习笔记(1)

diode 二极管PMOS低电平导通&#xff0c;NMOS高电平导通输出特性要掌握HPRI/BNI是high priority &#xff0c;binary&#xff0c;高位优先二进制编码器的意思&#xff0c;代表为8线-3线优先编码器 74148HPRI/BCD 表示BCD编码器&#xff0c;代表为10线-4线优先编码器 7414774138…