雨中的阳光Seattle

靠着很近的Redmond

[转]Lambda Expressions in C# 3.0 – A little Extension Methods and LINQ Too… 2007年09月20日

Filed under: C# — systembug @ 6:23 上午
del.icio.us Tags:

 

正在看有关Lambda表达式的相关文章,简明扼要的介绍了Lambda表达式。

 

 

Lambda Expressions in C# 3.0 – A little Extension Methods and LINQ Too…

by David Hayden

I spent some time with Lambda Expressions in C# 3.0 last night, and they are extremely cool. I haven’t played with Expression Trees yet, which sound like the coolest part of Lambda Expressions, but still, Lambda Expressions are cool just to help ease the burden of writing verbose Anonymous Methods.

Anonymous Methods

I love Anonymous Methods in C# 2.0. The idea behind anonymous methods it to write methods inline to the code so you don’t have to go through the trouble of declaring a formal named method. They are mainly used for small methods that don’t require any need for reuse.

Instead of declaring a separate method IsAbe to find Abe in a list of strings:

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find(IsAbe);
        Console.WriteLine(abe);
    }

    public static bool IsAbe(string name)
    {
        return name.Equals("Abe");
    }
}

You can declare an anonymous method inline to save you the trouble:

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find(delegate(string name)
                        {
                            return name.Equals("Abe");
                        });

        Console.WriteLine(abe);
    }
}

It can get a lot fancier than that, but that is basically the gist. Declare the method inline to the code for easy stuff not needing reuse, because it saves some typing and puts the method closer to where it is being used which helps with maintenance.

Lambda Expressions

Lambda Expressions make things even easier by allowing you to avoid the anonymous method and that annoying statement block:

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find((string name) => name.Equals("Abe"));

        Console.WriteLine(abe);
    }
}

Because Lambda Expressions are smart enough to infer variable types, I don’t even have to explicity mention that name is a string above. I can remove it for even more simplicity and write it as such:

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find(name => name.Equals("Abe"));

        Console.WriteLine(abe);
    }
}

Now there is no particular reason why I have to use name as my variable. Often developers will use 1 character variable names in Lambda Expressions just to keep things short. Here we replace name with p and all works the same.

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find(p => p.Equals("Abe"));

        Console.WriteLine(abe);
    }
}

Using a Customer Class

I used a list of strings above, but you can just as easily use a list of objects to do the same thing. I will take an abbreviated form of a Customer Class:

public class Customer
{
    public int Id;
    public string Name;
    public string City;

    public Customer(int id, string name, string city)
    {
        Id = id;
        Name = name;
        City = city;
    }
}

 and now use Lambda Expressions to find a particular Customer with a name of “Abe”.

class Program
{
    static void Main(string[] args)
    {            
        List<Customer> customers = new List<Customer>();
        customers.Add(new Customer(1,"Dave","Sarasota"));
        customers.Add(new Customer(2,"John","Tampa"));
        customers.Add(new Customer(3,"Abe","Miami"));

        Customer abe = customers.Find(c => c.Name.Equals("Abe"));
    }
}

Again, we could make things more obvious by explicity saying that c is of type Customer, but I wouldn’t bet on many people doing it 🙂 You could write the above as follows:

class Program
{
    static void Main(string[] args)
    {            
        List<Customer> customers = new List<Customer>();
        customers.Add(new Customer(1,"Dave","Sarasota"));
        customers.Add(new Customer(2,"John","Tampa"));
        customers.Add(new Customer(3,"Abe","Miami"));

        Customer abe = customers.Find((Customer c) => c.Name.Equals("Abe"));
    }
}

Combining Extension Methods and Lambda Expressions for Help In Finding Our Customer

Let’s say we have a custom CustomerCollection Class not within our control that doesn’t provide us an easy way to find a Customer:

public class CustomerCollection : IEnumerable<Customer>
{
    IEnumerable<Customer> _customers;

    public CustomerCollection(IEnumerable<Customer> customers)
    {
        _customers = customers;
    }

    public IEnumerator<Customer> GetEnumerator()
    {
        foreach (Customer customer in _customers)
            yield return customer;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _customers.GetEnumerator();
    }
}

Let’s add an Extension Method to the CustomerCollection Class, called GetCustomer:

public static class CustomerExtensions
{
    public static Customer GetCustomer(this CustomerCollection customers, Predicate<Customer> isMatch)
    {
        foreach (Customer customer in customers)
            if (isMatch(customer))
                return customer;
        return null;
    }
}

And now, we can use the CustomerCollection Class to easily find Abe like:

class Program
{
    static void Main(string[] args)
    {                    
        CustomerCollection collection = GetCustomers();    

        // Using my GetCustomer Method Extension that accepts my Lamda Expression...
        Customer abe = collection.GetCustomer(c => c.Name.Equals("Abe"));
    }
    
    // Pretend We Don't See This :)
    static CustomerCollection GetCustomers()
    {
        List<Customer> customers = new List<Customer>();
        customers.Add(new Customer(1,"Dave","Sarasota"));
        customers.Add(new Customer(2,"John","Tampa"));
        customers.Add(new Customer(3,"Abe","Miami"));
        
        return new CustomerCollection(customers);
    }
}

But This Is LINQ!

But, of course, since CustomerCollection implements IEnumerable<T>, in this case, IEnumerable<Customer>, screw the Extension Methods and just use LINQ:

class Program
{
    static void Main(string[] args)
    {                    
        CustomerCollection collection = GetCustomers();    

        // LINQ
        var abe = collection.Single(c => c.Name.Equals("Abe"));
    }
    
    // Pretend We Don't See This :)
    static CustomerCollection GetCustomers()
    {
        List<Customer> customers = new List<Customer>();
        customers.Add(new Customer(1,"Dave","Sarasota"));
        customers.Add(new Customer(2,"John","Tampa"));
        customers.Add(new Customer(3,"Abe","Miami"));
        
        return new CustomerCollection(customers);
    }
}
Advertisements
 

[转][黄忠成]Object Builder Application Block (3) 2007年09月10日

Filed under: C#,dotNet,IoC,Object Builder — systembug @ 3:19 上午

DependencyResolutionLocatorKey包装该对象实体,放入Locator中,如下所示。

private void RegisterObject(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
{
if (context.Locator != null)
    {
        ILifetimeContainer lifetime = context.Locator.Get<ILifetimeContainer>(
typeof(ILifetimeContainer), SearchMode.Local);
if (lifetime != ;null)
        {
            ISingletonPolicy singletonPolicy = context.Policies.Get<ISingletonPolicy>(
                typeToBuild, idToBuild);
if (singletonPolicy != null && singletonPolicy.IsSingleton)
            {
                context.Locator.Add(new DependencyResolutionLocatorKey(
                    typeToBuild, idToBuild), existing);
                lifetime.Add(existing);
//……………….
            }
        }
    }
}

以上流程是当该对象实体尚未建立时的流程,假如以BuildUp建立的对象已经存在于Locator中,那么SingletonStrategy的BuildUp方法将直接传回Locator中的对象实体。

public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
{
    DependencyResolutionLocatorKey key = new DependencyResolutionLocatorKey(
        typeToBuild, idToBuild);
if (context.Locator != null && context.Locator.Contains(key, SearchMode.Local))
    {
        TraceBuildUp(context, typeToBuild, idToBuild, “”);
return context.Locator.Get(key);
    }
return base.BuildUp(context, typeToBuild, existing, idToBuild);
}

PS:注意,SingletonStrategy在该对象已经存在于Locator中时,是直接回传,并不会调用后面如MethodExecutionStrategy、PropertySetterStrategy等Strategy。

5-2、TypeMappingStrategy

前面的章节早已使用过TypeMappingStrategy这个对象了,它主要负责『类型/id』的对应,例如将IDataProcessor接口类型的建立,替换成PromptDataProcessor类型,如程序28所示。

程序28

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_TypeMappingTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new TypeMappingStrategy());
            context.InnerChain.Add(new CreationStrategy());
            ITypeMappingPolicy policy = new TypeMappingPolicy(typeof(TestObject), null);
            context.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null);
            context.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
            ITestInterface obj1 = (ITestInterface)context.HeadOfChain.BuildUp(
                context, typeof(ITestInterface), null, null);
            obj1.SayHello();
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public interface ITestInterface
    {
void SayHello();
    }
public class TestObject : ITestInterface
    {
public void SayHello()
        {
            Console.WriteLine(“TEST”);
        }
    }
}

TypeMappingStrategy必须搭配TypeMappingPolicy对象使用,TypeMappingPolicy是一个实现ITypeMappingPolicy接口的对象,构造函数声明如下。

public TypeMappingPolicy(Type type, string id)

第一个参数是映像的实体类型,以本例来说就是TestObject,第二个参数是识别id,接着将其加入context.Policies中,如下所示。

context.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null)

当TypeMappingStrategy的BuildUp方法被调用时,它会以『类型/id』取得对应的ITypeMappingPolicy对象,透过它来取得对应的类型,之后将使用这个类型调用下一个Strategy的BuildUp方法,这就是Type Mapping的流程。

PS:注意,Type Mapping类型必须兼容,如接口->实现、基础类别->衍生类别。

5-3、BuildAwareStrategy

BuildAwareStrategy可以于实现IBuilderAware接口对象建立或释放时,调用对应的OnBuildUp或OnTearDown方法,如程序29所示。

程序29

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_BuildAwareTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new CreationStrategy());
            context.InnerChain.Add(new BuilderAwareStrategy());
            context.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
            TestObject obj = (TestObject)context.HeadOfChain.BuildUp(context,
typeof(TestObject), null, null);
            context.HeadOfChain.TearDown(context, obj);
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public class TestObject : IBuilderAware
    {
        #region IBuilderAware Members
public void OnBuiltUp(string id)
        {
            Console.WriteLine(“Object is build up”);
        }
public void OnTearingDown()
        {
            Console.WriteLine(“Object is TearDown”);
        }
        #endregion
    }
}

与其它的Strategy对象不同,BuilderAwareStrategy并不需要Policy对象的协助,它只是判断建立的对象是否实现了IBuilderAware接口。

5-4、BuildUp的第三、四个参数

截至目前为止,我们的例子在调用BuildUp方法时,第三及四个参数都传入null,这两个参数的用途究竟为何呢?这要先从BuildUp方法的宣告谈起。

object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild);

当我们于调用BuildUp方法指定existing为一对象实体时,CreationStrategy将不会建立任何新的对象,只会进行Singleton模式对象的相关动作,然后就调用下一个Strategy对象的BuildUp方法,简单的说!在CreationStrategy后的Strategy仍然会运行,例如Method Injection、Setter Injection都会再次运行,程序30可以协助读者理解这两个参数的用途。

程序30

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_ExistingTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new CreationStrategy());
            ConstructorPolicy policy = new ConstructorPolicy(new ValueParameter(typeof(string), “id”));
            context.Policies.Set<ICreationPolicy>(policy, typeof(TestObject), null);
            TestObject obj = (TestObject)context.HeadOfChain.BuildUp(context,
typeof(TestObject), null, null);
            TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context,
typeof(TestObject), obj, null);
if (obj == obj2)
                Console.WriteLine(“is same object.”);
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public class TestObject
    {
private string _id;
public string ID
        {
get
            {
return _id;
            }
        }
public TestObject(string id)
        {
            _id = id;
        }
    }
}

BuildUp的第四个参数则主导着ObjectBuilder的类型识别及对象识别机制,请先看程序31的例子。

程序31

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_IDTesting
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new CreationStrategy());
            context.InnerChain.Add(new PropertySetterStrategy());
            context.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
            PropertySetterInfo pi1 = new PropertySetterInfo(“ID”, new ValueParameter<string>(“ID1”));
            PropertySetterPolicy pp1 = new PropertySetterPolicy();
            pp1.Properties.Add(“ID”, pi1);
            context.Policies.Set<IPropertySetterPolicy>(pp1, typeof(TestObject), “TO1”);
            PropertySetterInfo pi2 = new PropertySetterInfo(“ID”, new ValueParameter<string>(“ID2”));
            PropertySetterPolicy pp2 = new PropertySetterPolicy();
            pp2.Properties.Add(“ID”, pi2);
            context.Policies.Set<IPropertySetterPolicy>(pp2, typeof(TestObject), “TO2”);
            TestObject obj1 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject),null, “TO1”);
            TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject),null, “TO2”);
            Console.WriteLine(obj1.ID);
            Console.WriteLine(obj2.ID);
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public class TestObject
    {
public string _id;
public string ID
        {
get
            {
return _id;
            }
set
            {
                _id = value;
            }
        }
    }
}

在这个例子中,我们建立了两个PropertySetterPolicy对象,分别以ID2、ID2为id加到了context.Policies中,当CreationStrategy建立对象时,它是以下面的程序代码来取得对应的Policy对象。

private object BuildUpNewObject(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
{
     ICreationPolicy policy = context.Policies.Get<ICreationPolicy>(typeToBuild, idToBuild);
     ………………
}

这段程序代码告诉我们一个重点,ObjectBuidler是以『类型/id』来做类型识别动作,也就是说TestObject+”ID1”、TestObject+”ID2”被ObjectBuilder视为两个不同的对象建立动作,你可以分别为其设定专属的Policy对象,也可以于调用BuildUp方法时,指定不同的id来建立同类型,但不同id的对象。另一个会使用『类型/id』来做识别的是DependencyResolutionLocatorKey对象,我们之前常使用它来完成Injection动作,而SingletonStrategy、DependencyParameter也都是运用它来完成所需完成的工作,其构造函数如下所示。

public DependencyResolutionLocatorKey(Type type, string id)

这意味着,当我们使用SingletonStrategy时,可以利用『类型/id』来建立两个同类型但不同id的Singleton对象,如程序32所示。

程序32

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_SingletonTwoTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new SingletonStrategy());
            context.InnerChain.Add(new CreationStrategy());
            context.Policies.Set<ISingletonPolicy>(new SingletonPolicy(true), typeof(TestObject), “ID1”);
            context.Policies.Set<ISingletonPolicy>(new SingletonPolicy(true), typeof(TestObject), “ID2”);
            context.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
            TestObject obj1 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject), null, “ID1”);
            TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject), null, “ID2”);
if (obj1 == obj2)
                Console.WriteLine(“Singleton”);
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public class TestObject
    {
    }
}

这个例子将TestObject+”ID1”、TestObject+”ID2”设定为两个不同的Singleton对象,所以当首次建立并指定id时,所建立出来的两个对象是相异的,也就是说,可以利用『类型/id』来建出两个Singleton系统。

5-5、StrategyList

在本文一开始的范例中,我们使用Builder对象来建立对象,它使用了一个StrategyList对象来处理Strategy串行,这个对象提供了两个重要的方法,一是MakeStrategyChain,它会将StrategyList中的Strategy输出成BuilderStrategyChain对象,这是一个实现了IBuilderStrategyChain接口的对象,也是IBuilderContext所要求的Strategy串行对象。第二个方法是MakeReverseStrategyChain,它会将内含的Strategys反相排序后输出成BuilderStrategyChain对象,这个动作是为了准备TearDown时所需的Strategy串行,还记得前面提过,TearDown的Strategy顺序应该与建立时完全相反,这样才能让对象与其相关的子对象适当的释放。

5-6、TStageEnum

StrategyList是一个泛型对象,它接受一个Enum类型,会依照Enum中所定义的元素来建立Strategy串行或是反相排序,要了解这个设计的原意,我们得先看看ObjectBuilder中所预定义,用来指定给StrategyList的列举。

public enum BuilderStage
{
    PreCreation,
    Creation,
    Initialization,
    PostInitialization
}

读者可以查觉,这与我们先前将Strategy分成四种类型的方式相呼应,StrategyList会依据PreCreation、Creation、Initialization、PostInitialization的顺序来产生BuilderStrategyChain对象,这样就不会因为错置Strategy的顺序,导致程序不正常(例如,先加入CreationStrategy再加入TypeMappingStrategy时,TypeMappingStrategy将无法运作)。Builder对象充份展示了BuilderStage与StrategyList的运用方式。

public Builder(IBuilderConfigurator<BuilderStage> configurator)
{
    Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);
    Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);
    Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);
    Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);
    Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
if (configurator != nulll)
        configurator.ApplyConfiguration(this);
}

只要传入的BuilderStage是正确的,不管TypeMappingStrategy是加在CreationStrategy前面或后面,皆可正常运作。不过同一类型的Strategy,但有顺序需求的情况下,仍然要小心调整顺序,程序32示范了运用BuilderStage所带来的优点。

程序32

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_StrategyListTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilder builder = new MyBuilder();
            ITypeMappingPolicy policy = new TypeMappingPolicy(typeof(TestObject), null);
            builder.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null);
            ITestInterface obj1 = builder.BuildUp<ITestInterface>(new Locator(), null, null);
            Console.Read();
        }
    }
public class MyBuilder : BuilderBase<BuilderStage>
    {
public MyBuilder()
            : this(null)
        {
        }
public MyBuilder(IBuilderConfigurator<BuilderStage> configurator)
        {
            Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);
            Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);
            Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);
            Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);
            Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
if (configurator != null)
                configurator.ApplyConfiguration(this);
        }
    }
public interface ITestInterface
    {
    }
public class TestObject : ITestInterface
    {
    }
}

5-6、PolicyList

BuilderContext所定义的Policies对象类型为PolicyList,PolicyList对象以Dictionary对象来储存设计者所加入的Policy对象,其中用来作为键值的BuilderPolicyKey类别构造函数如下。

public BuilderPolicyKey(Type policyType, Type typePolicyAppliesTo, string idPolicyAppliesTo)

第一个参数为policyType,也就是ICrationPolicy、ITypeMappingPolicy等之类,第二个参数是对应的类型,第三个参数则是id。设计者可以调用PolicyList.Set方法来加入一个Policy至内部的存储器中,该方法会依据传入的参数建立BuilderPolicyKey做为键值,然后将Policy加到Dictionary中,如下所示。

public void Set(Type policyInterface, IBuilderPolicy policy, Type typePolicyAppliesTo,
string idPolicyAppliesTo)
{
    BuilderPolicyKey key = new BuilderPolicyKey(policyInterface, typePolicyAppliesTo, idPolicyAppliesTo);
lock (lockObject)
    {
        policies[key] = policy;
    }
}

另一个泛型类型的Set方法也可以达到同样的效果。

public void Set<TPolicyInterface>(TPolicyInterface policy, Type typePolicyAppliesTo,
string idPolicyAppliesTo) where TPolicyInterface : IBuilderPolicy
{
     Set(typeof(TPolicyInterface), policy, typePolicyAppliesTo, idPolicyAppliesTo);
}

设计者可以透过PolicyList.Get方法来取得对应的Policy对象,该方法如下所示。

public TPolicyInterface Get<TPolicyInterface>(Type typePolicyAppliesTo, string idPolicyAppliesTo)
    where TPolicyInterface : IBuilderPolicy
{
return (TPolicyInterface)Get(typeof(TPolicyInterface), typePolicyAppliesTo, idPolicyAppliesTo);
}
public IBuilderPolicy Get(Type policyInterface, Type typePolicyAppliesTo, string idPolicyAppliesTo)
{
    BuilderPolicyKey key = new BuilderPolicyKey(policyInterface,
        typePolicyAppliesTo, idPolicyAppliesTo);
lock (lockObject)
    {
        IBuilderPolicy policy;
if (policies.TryGetValue(key, out policy))
return policy;
        BuilderPolicyKey defaultKey = new BuilderPolicyKey(policyInterface, null, null);
if (policies.TryGetValue(defaultKey, out policy))
return policy;
return null;
    }
}

SetDefault则可以用一个Policy来提供给所有类型使用,Get方法在找不到对应『类型/id』对应的Policy时,就会以该Policy回传。

六、Locator

ObjectBuilder利用Locator对象来实现Service Locator,也利用Locator来进行Dependency Injection,在ObjectBuilder的架构上,Locator有两种类型,一是Readonly Locator,顾名思义,这类Locator只允许读取、不允许新增。二是ReadWriteLocator,它是允许新增、读取类的Locator。我们可以从Visual Studio 2005的Class Diagram来观察ObjectBuilder中的Locator阶层架构。

图7

6-1、Readonly Locator

ObjectBuidler定义了一个IReadableLocator接口,所有的Locator都必须直接或间接实现此接口,内建实现此接口的类别是ReadableLocator,它是一个抽象类。真正完成实现可用的是ReadOnlyLocator,这个Locator只允许读取,不允许新增。

6-2、ReadWrite Locator

ObjectBuilder中支持读与写的Locator是ReadWriterLocator,与ReadOnlyLocator一样,它也是一个抽象类,真正完成实现的是Locator类别。附带一提,虽然Locator定义了蛮清楚的阶层,但是BuilderContext只支持实现IReadWriterLocator接口的Locator。

6-3、WeakRefDictionary and Locator

Locator类别是我们一直都在使用的Locator,它是一个继承自ReadWriterLocator的类别,值得一提的是,它使用一个WeakRefDictionary来储存设计者所放入Locator的对象,WeakRefDictionary内部对于每个元素都会以WeakReference封装,这意味着,Locator中的元素并无法保证一直都存在,因为CLR会在内存拮据时,先行释放WeakRefernce所封装的对象,这点读者必须谨记。

七、Extending ObjectBuilder

ObjectBuilder除了支持三种Dependency Injection模式、Service Locator之外,最大的魅力应该来自于具高度延展性的架构,设计者可以透过撰写Strategy、Policy、Locator等类别来参与对象的建立动作,本章以两个范例来证明这点,一是EventSetterStrategy,它提供Event Injection功能,二是PoolStrategy,提供Pool模式的对象建立。

7-1、EventSetterStrategy

ObjectBuidler提供了Constructor Injection、Interface Injection(Method Ijection)、Setter Injection(Property Injection)三种Injection模式,虽然ObjectBuilder只提供了Propety式的Setter Injection,不过我们可以藉助于ObjectBuilder高度的延展性架构,让ObjectBuidler也能支持Event Injection。

  • IEventSetterInfo

Event Injection与Property Injection同属Setter Injection模式,两者运作的模式也极为相似,ObjectBuilder在Property Injection部份是由ProperySeterInfo、PropertySetterPolicy及PropertySetterStrategy三个类别所构筑而成,我们可以依循这个既定架构,实现Event Injection功能。首要必须定义一个IEventSetterInfo接口,这相对于IPropertySetterInfo接口之于Property Injection。

程序33

public interface IEventSetterInfo
{
object GetValue(IBuilderContext context, Type type, string id, EventInfo propInfo);
     EventInfo SelectEvent(IBuilderContext context, Type type, string id);
}

IEventSetterInfo接口定义了两个方法,SelectEvent方法是用来取得欲Injection事件的EventInfo对象,EventSetterStrategy会调用此方法来取得欲Injection事件的EventInfo对象,然后透过EventInfo.AddHandler来进行注入动作,这个注入动作所使用的值是透过调用IEventSetterInfo.GetValue方法来取得,此接口的实现程序代码如34。

程序34

public sealed class EventSetterInfo : IEventSetterInfo
{
private string _name = null;
private IParameter _value = null;
    #region IEventSetterInfo Members
public object GetValue(IBuilderContext context, Type type, string id, EventInfo propInfo)
    {
return _value.GetValue(context);
    }
public EventInfo SelectEvent(IBuilderContext context, Type type, string id)
    {
return type.GetEvent(_name);
    }
    #endregion
public EventSetterInfo(string name, IParameter value)
    {
        _name = name;
        _value = value;
    }
}

  • IEventSetterPolicy

前面提过,Strategy是与类型无关的设计,因此需要Policy的协助,我们所设计的EventSetterStrategy也是一样,Event Injection必须具备针对不同『类型/id』进行Event Injection的能力,所以必须设计一个IEventSetterPolicy接口,该接口必须直接或间接继承自IBuilderPolicy接口,这是ObjectBuilder对于Policy的规范。

程序35

public interface IEventSetterPolicy : IBuilderPolicy
{
    Dictionary<string, IEventSetterInfo> Events { get;}
}

针对同一『类型/id』对象可能需要注入一个以上的事件,此接口定义了一个Dictionary<string,IEventSetterInfo>对象,让设计者可以指定一个以上的Event Injection动作,36是此接口的实现。

程序36

public sealed class EventSetterPolicy : IEventSetterPolicy
{
private Dictionary<string, IEventSetterInfo> _events = new Dictionary<string, IEventSetterInfo>();
     #region IEventPolicy Members
public Dictionary<string, IEventSetterInfo> Events
     {
get
         {
return _events;
         }
     }
     #endregion
}

  • EventSetterStrategy

完成了基础类别的设计与实现后,剩下的就是Strategy,也就是EventSetterStrategy的设计与实现了,设计上,EventSetterStrategy只有一个任务,就是于BuildUp方法被调用时,透过『类型/id』经由context.Locator取得对应的IEventSetterPolicy对象,再透过它取得欲进行注入动作的IEventSetterInfo对象,接着调用IEventSetterInfo.SelectEvent方法取得EventInfo对象,最后调用IEventSetterInfo.GetValue取得欲注入的Event Handler对象,然后调用EventInfo.AddHandler方法完成注入动作。

程序37

public class EventSetterStrategy : BuilderStrategy
{
public override object BuildUp(IBuilderContext context, Type typeToBuild,
object existing, string idToBuild)
    {
if (existing != null)
            InjectEvents(context, existing, idToBuild);
return base.BuildUp(context, typeToBuild, existing, idToBuild);
    }
private void InjectEvents(IBuilderContext context, object obj, stringg id)
    {
if (obj == null)
return;
        Type type = obj.GetType();
        IEventSetterPolicy policy = context.Policies.Get<IEventSetterPolicy>(type, id);
if (policy == null)
return;
foreach (IEventSetterInfo eventSetterInfo in policy.Events.Values)
        {
            EventInfo eventInfo = eventSetterInfo.SelectEvent(context, type, id);
if (eventInfo != null)
            {
if (TraceEnabled(context))
                    TraceBuildUp(context, type, id, “Event Setter”, eventInfo.Name);
                eventInfo.AddEventHandler(obj,
                    eventSetterInfo.GetValue(context, type, id, eventInfo) as Delegate);
           }
        }
    }
}

  • Testing

EventSetterStrategy的使用方式与PropertySetterStrategy相似,如38所示。

程序38

using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace EventSetterTest
{
class Program
    {
static void Main(string[] args)
        {
            Builder builder = new Builder();
            builder.Strategies.AddNew<EventSetterStrategy>(BuilderStage.Initialization);
            IEventSetterPolicy policy = new EventSetterPolicy();
            EventHandler handler = new EventHandler(CallHandler);
            policy.Events.Add(“Call”, new EventSetterInfo(“Call”,
new ValueParameter(typeof(EventHandler), handler)));
            builder.Policies.Set<IEventSetterPolicy>(policy, typeof(TestObject), null);
            TestObject obj = builder.BuildUp<TestObject>(new Locator(), null, null);
            obj.RaiseCall();
            Console.ReadLine();
        }
static void CallHandler(object sender, EventArgs args)
        {
            Console.WriteLine(“Called”);
        }
    }
public class TestObject
    {
private EventHandlerList _events = new EventHandlerList();
private static object _onCall = new object();
public event EventHandler Call
        {
            add
            {
                _events.AddHandler(_onCall, value);
            }
            remove
            {
                _events.RemoveHandler(_onCall, value);
            }
        }
protected virtual void OnCall(EventArgs args)
        {
            EventHandler handler = (EventHandler)_events[_onCall];
if (handler != null)
                handler(this, args);
        }
public void RaiseCall()
        {
            OnCall(EventArgs.Empty);
        }
    }
}

图8是此程序的运行结果。

图8

7-2、PoolStrategy

GoF的书中,提出了三种对象管理Pattern,一是Singleton,意味着对象一旦建立后,就存放于某个存储器中,之后所有要求对象的建立动作,都将会获得同样的对象实体,在ObjectBuilder中实现这个Pattern的就是SingletonStrategy。第二个Pattern是SingleCall模式,意味所有的对象建立动作都会获得一个新的对象实体,跟new、create等语言所定义的对象建立模式相同,在Service模式中,SingleCall也意味着Service对象会在要求到达时建立,结束后就立即的释放,这两个模式都可以用ObjectBuilder轻易的实现。第三种Pattern就是Pool,也就是说在特定存储器中维持一定数量的对象实体,当要求对象建立动作时,系统会遍寻存储器中的对象,如果有对象标示为未使用状态,那么系统就回传该对象,并将该对象标示为使用中,本节将实现一个PoolStrategy,让ObjectBuilder可以具备Pool的能力。

  • PoolFactory

Pool Pattern的核心就是一个可以于存储器中管理对象的能力,此处使用笔者书中所设计的PoolFactory类别来完成这个目的。

程序39

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace Orphean.WinFormHelper.Framework.Factorys
{
///<summary>
/// a interface to be implement by Object Factory,
/// DAL use object factory to speed object constructing.
///</summary>
public interface IObjectFactory
    {
///<summary>
/// acquire a object.
///</summary>
///<param name=”type”>object Type</param>
///<returns>object</returns>
object AcquireObject(Type type);
///<summary>
/// release a object.
///</summary>
///<param name=”obj”>a object to releasing</param>
void ReleaseObject(object obj);
    }
public sealed class PoolObjectFactory : IObjectFactory, IDisposable
    {
class PoolData
        {
public bool InUse = falsee;
public object obj;
        }
private IList _storage;
private int _max = 100;
private bool _limit = false;
private IBuilderContext _context = null;
public PoolObjectFactory(IBuilderContext context, int max, bool limit, IList storage)
            : this(context)
        {
            _max = max;
            _limit = limit;
            _storage = storage;
        }
public PoolObjectFactory(IBuilderContext context)
        {
            _context = context;
        }
private PoolData GetPoolData(object obj)
        {
lock (_storage.SyncRoot)
            {
for (int i = 0; i < _storage.Count; i++)
                {
                    PoolData p = (PoolData)_storage[i];
if (p.obj == obj)
return p;
                }
            }
return null;
        }
private object GetObject(Type type)
        {
lock (_storage.SyncRoot)
            {
if (_storage.Count > 0)
                {
if (((PoolData)_storage[0]).obj.GetType() != type)
throw new Exception(
string.Format(“the Pool Factory only for Type :{0}”, _storage[0].GetType().Name));
                }
for (int i = 0; i < _storage.Count; i++)
                {
                    PoolData p = (PoolData)_storage[i];
if (!p.InUse)
                    {
                        p.InUse = true;
return p.obj;
                    }
                }
if (_storage.Count > _max && _limit)
throw new Exception(“max limit is arrived.”);
object obj = _context.HeadOfChain.BuildUp(_context, type, null, null);
                PoolData p1 = new PoolData();
                p1.InUse = true;
                p1.obj = obj;
                _storage.Add(p1);
return obj;
            }
        }
private void PutObject(object obj)
        {
            PoolData p = GetPoolData(obj);
if (p != null)
                p.InUse = false;
        }
        #region IObjectFactory Members
public object AcquireObject(Type type)
        {
return GetObject(type);
        }
public void ReleaseObject(object obj)
        {
if (_storage.Count > _max)
            {
if (obj is IDisposable)
                    ((IDisposable)obj).Dispose();
                PoolData p = GetPoolData(obj);
lock (_storage.SyncRoot)
                    _storage.Remove(p);
return;
            }
            PutObject(obj);
        }
        #endregion
        #region IDisposable Members
public void Dispose()
        {
lock (_storage.SyncRoot)
            {
for (int i = 0; i < _storage.Count; i++)
                {
                    PoolData p = (PoolData)_storage[i];
if (p.obj is IDisposable)
                        ((IDisposable)p.obj).Dispose();
                }
            }
        }
        #endregion
    }
}

本文的重点在于ObjectBuilder的应用与延伸,所以此处就不在赘述PoolFactory的实现细节。

  • IPoolPolicy

PoolStrategy在架构上与SingletonStrategy类似,我们必须设计一个IPoolPolicy接口,该接口的定义如程序40。

程序40

public interface IPoolPolicy : IBuilderPolicy
{
bool IsPool { get;}
}

此接口只定义了一个Pool属性,用来告诉PoolStrategy那个『类型/id』是需要Pool,那个又是不需要的,虽然设计者可以针对要Pool的『类型/id』来指定IPoolPolicy,如果有特定对象不需要Pool动作,那就不指定IPoolPocy即可,但是我们无法排除一种情况,那就是系统里大多数对象都需要Pool,仅有特定的对象不需要Pool,此时要特别对一个个对象设定IPoolPolicy的话,会相当的繁琐。此时设计者可以以SetDefault来加入IPoolPolicy对象,将所有对象标示为可Pool,再针对不需要Pool的对象来指定IPoolPolicy。程序41是实现此接口的程序代码列表。

程序41

public class PoolPolicy : IPoolPolicy
{
private bool _isPool = false;
    #region IPoolPolicy Members
public bool IsPool
    {
get
        {
return _isPool;
        }
    }
    #endregion
public PoolPolicy(bool isPool)
    {
        _isPool = isPool;
    }
}

  • PoolStrategy

PoolStrategy必须在BuildUp方法运用PoolFactory来取得要求的对象,在设计上,我们会为每个『类型/id』建立独立的PoolFactory对象,这意味着每个『类型/id』的对象数量是独立管理的。

程序42

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
using Orphean.WinFormHelper.Framework.Factorys;
namespace OB_PoolStrategy
{
public class PoolStrategy : BuilderStrategy
    {
private WeakRefDictionary<object, object> _factoryMap =
new WeakRefDictionary<object, object>();
private bool _poolObjectCreating = false;
public override object BuildUp(IBuilderContext context, Type typeToBuild,
object existing, string idToBuild)
        {
if (!_poolObjectCreating)
            {
                IPoolPolicy policy = context.Policies.Get<IPoolPolicy>(typeToBuild, idToBuild);
if (policy != null && policy.IsPool)
                {
                    PoolLocatorKey key = new PoolLocatorKey(typeToBuild, idToBuild);
                    PoolObjectFactory factory = null;
if (context.Locator.Contains(key))
                    {
                        factory = context.Locator.Get<PoolObjectFactory>(key);
lock (this)
                        {
                            _poolObjectCreating = true;
try
                            {
                                existing = factory.AcquireObject(typeToBuild);
                            }
finally
                            {
                                _poolObjectCreating = false;
                            }
                        }
                    }
else
                    {
                        factory = new PoolObjectFactory(context, 15, false, new ArrayList());
                        _poolObjectCreating = true;
try
                        {
                            existing = factory.AcquireObject(typeToBuild);
                        }
finally
                        {
                            _poolObjectCreating = false;
                        }
                        context.Locator.Add(key, factory);
                    }
if (!_factoryMap.ContainsKey(existing))
                        _factoryMap.Add(existing, factory);
                }
            }
return base.BuildUp(context, typeToBuild, existing, idToBuild);
        }
public override object TearDown(IBuilderContext context, object item)
        {
if (_factoryMap.ContainsKey(item))
            {
                PoolObjectFactory factory = _factoryMap[item] as PoolObjectFactory;
if (factory != null)
                    factory.ReleaseObject(item);
                _factoryMap.Remove(item);
            }
return base.TearDown(context, item);
        }
    }
public sealed class PoolLocatorKey
    {
private Type type;
private string id;
public PoolLocatorKey()
            : this(null, null)
        {
        }
public PoolLocatorKey(Type type, string id)
        {
this.type = type;
this.id = id;
        }
public string ID
        {
get { return id; }
        }
public Type Type
        {
get { return type; }
        }
public override bool Equals(object obj)
        {
            PoolLocatorKey other = obj as PoolLocatorKey;
if (other == null)
return false;
return (Equals(type, other.type) && Equals(id, other.id));
        }
public override int GetHashCode()
        {
int hashForType = type == null ? 0 : type.GetHashCode();
int hashForID = id == null ? 0 : id.GetHashCode();
return hashForType ^ hashForID;
        }
    }
}

在BuildUp方法被调用时,PoolStrategy会透过context.Policies取得『类型/id』对应的IPoolPolicy对象,判断此次建立动作是否使用Pool,是的话就以『类型/id』至Locator中取出PoolFactory,如果Locator已经有该PoolFactory时,就直接调用PoolFactory.AcquireObject方法来取得对象实体,如果Locator中无对应的PoolFactory时,就建立一个并放入Locator中。在这个建立流程中有几个重点,第一!我们将PoolFactory储存在Locator中,因此需要一个类似DependencyResolutionLocatorKey的对象,用来做为由Locator取出PoolFactory的键值,这个对象必须覆载Equal、GetHashCode两个方法,因为Locator会调用这两个方法来比对键值,这个对象就是PoolLocatorKey。第二!PoolFactory在存储器中没有可使用对象时,会调用BuilderContext.HeadChain.BuildUp方法来建立该对象,这会引发重进入的问题,BuilderContext.HeadChain.BuildUp方法将会再次触发PoolStrategy的BuildUp,而这里又会再次调用BuilderContext.HeadChain.BuildUp,造成重入的问题,所以此处利用一个旗标:poolObjectCreating来解决这个问题。第三!PoolStrategy必须在TearDown方法被调用时,调用PoolFactory.ReleaseObject来将该对象归还,此时会遭遇到一个问题,因为TearDown方法只会传入对象实体,没有id的信息,这使得PoolStrategy无法于此处取得对应的PoolFactory对象,为了解决此问题,PoolStrategy宣告了一个_factoryMap对象,它是一个WeakRefDictionary<object, object>类别对象,在对象实体于BuildUp方法被建立后,PoolStrategy会将object/PoolFactory成对放入_factoryMap中,这样就能于TearDown时以对象实体至_factoryMap中取出对应的PoolFactory对象了。

  • Testing

PoolStrategy的使用方式与SingletonStrategy类似,程序43是应用的程序代码列表。

程序43

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_PoolStrategy
{
class Program
    {
static void Main(string[] args)
        {
            Builder builder = new Builder();
            builder.Strategies.AddNew<PoolStrategy>(BuilderStage.PreCreation);
            IPoolPolicy policy = new PoolPolicy(true);
            builder.Policies.Set<IPoolPolicy>(policy, typeof(TestObject), null);
            Locator locator = new Locator();
            TestObject obj1 = builder.BuildUp<TestObject>(locator, null, null);
            TestObject obj2 = builder.BuildUp<TestObject>(locator, null, null);
            builder.TearDown<TestObject>(locator, obj1);
            builder.TearDown<TestObject>(locator, obj2);
            TestObject obj3 = builder.BuildUp<TestObject>(locator, null, null);
if (obj3 == obj1 || obj3 == obj2)
                Console.WriteLine(“Pooled”);
            Console.ReadLine();
        }
    }
public class TestObject
    {
    }
}

图9是执行结果。

图9

 

[转][黄忠成]Object Builder Application Block (2)

Filed under: C#,dotNet,IoC,Object Builder — systembug @ 3:13 上午

[转]Object Builder Application Block

文/黄忠成 ;2006/9/21

原文链接:http://blog.csdn.net/Code6421/archive/2006/09/25/1282150.aspx

整理:吕震宇

三、ObjectBuilder Application Block

ObjectBuilder一开始出现于Microsoft所提出的Composite UI Application Block,主司对象的建立及释放工作, 它实现了本文前面所提及的Dependency Injection概念,同时在架构上提供了高度的延展性。运用ObjectBuilder来建立对象,设计师可以透过程序或组态文件,对对象建立与释放的流程进行细部的调整,例如改变对象建立时所调用的Constructor(构造函数),调整传入的参数,于对象建立后调用特定方法等等。鉴于ObjectBuilder的功能逐渐完整,加上社群对于Dependency Injection实现对象的强烈需求,Microsoft正式将ObjectBuilder纳入Enterprise Library 2006中,并修改Caching、Logger、Security、Data Access等Application Block的底层,令其于ObjectBuilder整合,以此增加这些Application Block的延展性。就官方文件的说明,ObjectBuilder Application Block提供以下的功能。

  • 允许要求一个抽象对象或接口,ObjectBuilder会依据程序或组态文件的设定,传回一个实体对象。
  • 回传一个既存对象,或是每次回传一个新的对象(多半用于Dependency、Singleton情况,稍后会有详细说明)。
  • 透过特定的Factory建立一个对象,这个Factory可以依据组态文件的设定来建立对象(CustomFactory,隶属于Enterprise Common Library)。
  • 当物件拥有一个以上的构造函数时,依据已有的参数,自动选取兼容的构造函数来建立要求的对象。(Consturctor Injection)
  • 允许对象于建立后,透过程序或组态文件来赋值至属性,或是调用特定的方法。(Setter Injection、Interface Injection)
  • 提供一组Attribute,让设计师可以指定需要Injection的属性,亦或是于对象建立后需要调用的方法,也就是使用Reflection来自动完成Injection动作。
  • 提供IBuilerAware接口,实现此接口的对象,ObjectBuilder会于建立该对象后,调用OnBuildUp或是OnTearDown方法。
  • 提供TearDown机制,按建立对象的流程,反向释放对象。

对于多数读者来说,这些官方说明相当的隐诲,本文尝试由架构角度切入,讨论ObjectBuidler的主要核心概念,再透过实现让读者们了解,该如何使用ObjectBuidler。

3-1、The Architecture of Object Builder

图2

图2是ObjectBuilder中四个主要核心对象的示意图,BuidlerContext是一个概念型的环境对象,在这个对象中,包含着一组Strategys对象,一组Polices对象,一个Locator对象, ObjectBuidler采用Strategys Pipeline(策略流)概念,设计师必须透过Strategy串行来建立对象,而Strategy会透过Polices来寻找『类型/id』对应的Policy对象,使用 它来协助建立指定的对象。此处有一个必须特别提出来讨论的概念,Strategy在架构上是与类型无关的,每个BuidlerContext会拥有一群Strategys对象,我们透过这个Strategys对象来建立任何类型的对象,不管建立的对象类型为何,都会通过这个Strategys Pipeline。这意味着,当我们希望于建立A类型对象后调用方法A1,于建立B类型对象后调用方法 B1时,负责调用方法的Strategy对象会需要一个机制来判别该调用那个方法,那就是Policy对象,BuilderContext中拥有一个Polices对象,其中存放着与『类型/id』对应的Policy对象,如图3所示。

图3

值得一提的是,Policy是以Type/id方式,也就是『类型/id』方式来存放,这种做法不只可以让不同类型拥有各自的Policy,也允许同类型但不同id拥有各自的Policy。ObjectBuilder中的最后一个元素是Locator,Locator对象在ObjectBuidler中扮演着前述的Service Locator角色,设计师可以用Key/Value的方式,将对象推入Locator中,稍后再以Key值来取出使用。

3-2、Strategys

ObjectBuilder内建了许多Strategy,这些Strategy可以大略分成四种类型,如图4。

图4

  • Pre-Creation Strategy

Pre-Creation意指对象被建立前的初始动作,参与此阶段的Strategy有:TypeMappingStrategy、PropertyReflectionStrategy、ConstructorReflectionStrategy、MethodReflectionStrategy及SingletonStrategy,稍后我们会一一检视 它们。

  • Creation Strategy

Creation类型的Strategy主要工作在于建立对象,它会利用Pre-Creation Strategys所准备的参数来建立对象,ObjectBuilder就是运用Pre-Creation的ConstructorReflectionStrategy及CreationStrategy来完成Constructor Injection动作。

  • Initialization Strategy

当对象建立后,会进入初始化阶段,这就是Initialization Strategy阶段,在此阶段中,PropertySetterStrategy会与PropertyReflectionStrategy合作,完成Setter Injection。而MethodExecutionStrategy则会与MethodReflectionStrategy合作,在对象建立后,调用特定的方法,也就是Method Injection(视使用方式,Interface Injection是以此种方式完成的)。

  • Post-Initialization Strategy

在对象建立并完成初始化动作后,就进入了Post-Initialization Strategy阶段,在此阶段中,BuilderAwareStrategy会探询已建立的对象是否实现了IBuilderAware接口,是的话就调用IBuilderAware.OnBuildUp方法。

  • 关于对象释放

先前曾经提过,ObjectBuidler在建立对象时,会一一调用所有Strategy来建立对象,同样的!当释放对象时,ObjectBuilder也会进行同样的动作,不过方向是相反的,在内建的Strategy中,只有BuilderAwareStrategy会参与对象释放的动作,在对象释放时,BuilderAwareStrategy会探询欲释放的对象是否实现了IBuidlerAware接口,是的话就调用IBuidlerAware.OnTearDown方法。

3-3、A Simple Application

再怎么详细的说明,少了一个实例就很难让人理解,本节以一个简单的ObjectBuidler应用实例开始,一步步带领读者进入ObjectBuilder的世界。

程序10

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace SimpleApp
{
class Program
    {
static void Main(string[] args)
        {
            Builder builder = new Builder();
            TestObject obj = builder.BuildUp<TestObject>(new Locator(), null, null);
            obj.SayHello();
            Console.ReadLine();
        }
    }
public class TestObject
    {
public void SayHello()
        {
            Console.WriteLine(“TEST”);
        }
    }
}

这是一个相当阳春的例子,在程序一开始时建立了一个Builder对象,它是ObjectBuilder所提供的Facade对象,其会预先建立一般常用的Strategy串行,并于BuilderUp方法被调用时,建立一个BuilderContext对象,并将Srategy串行及Polices串行指定给该BuilderContext,然后进行对象的建立工作。

  • How Object Creating

要了解前面的例子中,TestObject对象究竟是如何被建立起来的,首先必须深入Builder对象的建构动作。

private StrategyList<TStageEnum> strategies = new StrategyList<TStageEnum>();
public BuilderBase()
{
}
public PolicyList Policies
{
get { return policies; }
}
public StrategyList<TStageEnum> Strategies
{
get { return strategies; }
}
public Builder(IBuilderConfigurator<BuilderStage> configurator)
{
    Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);
    Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);
    Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);
    Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);
    Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
if (configurator != null)
        configurator.ApplyConfiguration(this);
}

当Buidler对象被建立时,其构造函数会将前面所提及的几个Strategys加到Strategies这个StrategyList Collection对象中,待BuildUp方法被调用时指定给新建立的BuilderContext对象。

public TTypeToBuild BuildUp<TTypeToBuild>(IReadWriteLocator locator,
string idToBuild, object existing, params PolicyList[] transientPolicies)
{
return (TTypeToBuild)BuildUp(locator, typeof(TTypeToBuild), idToBuild, 
        existing, transientPolicies);
}
public virtual object BuildUp(IReadWriteLocator locator, Type typeToBuild,
string idToBuild, object existing, params PolicyList[] transientPolicies)
{
………………..
return DoBuildUp(locator, typeToBuild, idToBuild, existing, transientPolicies);
……………….
}
private object DoBuildUp(IReadWriteLocator locator, Type typeToBuild, 
string idToBuild, object existing, PolicyList[] transientPolicies)
{
    IBuilderStrategyChain chain = strategies.MakeStrategyChain();
………………..
    IBuilderContext context = MakeContext(chain, locator, transientPolicies);
………………..                           
object result = chain.Head.BuildUp(context, typeToBuild, existing, idToBuild);
………………..
}
private IBuilderContext MakeContext(IBuilderStrategyChain chain,
        IReadWriteLocator locator, params PolicyList[] transientPolicies)
{
………………..
return new BuilderContext(chain, locator, policies);
}

当Builder的泛型方法BuildUp方法被调用后,其会调用非泛型的BuildUp方法,该方法会调用DoBuildUp方法,此处会透过strategies(先前于Builder构造函数时初始化的StrategyList对象)来取得Strategys串行,并指定给稍后由MakeContext方法建立的BuilderContext,最后调用Strategy串行中第一个Strategy的BuildUp方法来进行对象的建立动作。在这一连串的动作中,我们可以厘清几个容易令人混淆的设计,第一!我们是透过Strategy串行,也就是IBuidlerStrategyChain.Head.BuildUp来建立对象,这个Head属性就是Strategy串行中的第一个Strategy。第二!BuilderContext的作用在于,于调用各个Strategy.BuildUp方法时,给予 它们存取此次建立动作所使用的Strategys及Policies等对象的机会。

  • Policy物件的用途

现在,我们弄清楚了Strategy的用途,BuilderContext的真正涵意,但还有两个元素尚未厘清,其中之一就是Policy对象,前面曾经稍微提过,Strategy是与类型无关的设计概念,因此为了针对不同类型做个别的处理,我们需要另一个与类型相关的设计,那就是Policy对象,要确认这点,必须重返Builder的构造函数。

public Builder(IBuilderConfigurator<BuilderStage> configurator)
{
………………
    Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
……………..
}

这里调用了Policies的SetDefault方法,Policies是一个PolicyList对象,其提供了推入(Set、SetDefault)及取出(Get)方法,允许设计者针对所有『类型/id』及特定『类型/id』指定对应的IBuilderPolicy对象,那这有什么用呢?这个问题可以由CreationStrategy类别中的以下这段程序代码来回答。

public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
{
if (existing != null)
        BuildUpExistingObject(context, typeToBuild, existing, idToBuild);
else
        existing = BuildUpNewObject(context, typeToBuild, existing, idToBuild);
return base.BuildUp(context, typeToBuild, existing, idToBuild);
}
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.SerializationFormatter)]
private object BuildUpNewObject(IBuilderContext context, Type typeToBuild,
object existing, string idToBuild)
{
ICreationPolicy policy = context.Policies.Get<ICreationPolicy>(typeToBuild, idToBuild);
…………………….
    InitializeObject(context, existing, idToBuild, policy);
return existing;
}
private void InitializeObject(IBuilderContext context, object existing, string id, ICreationPolicy policy)
{
…………………….
    ConstructorInfo constructor = policy.SelectConstructor(context, type, id);
…………………….
object[] parms = policy.GetParameters(context, type, id, constructor);
…………………….             
    method.Invoke(existing, parms);
}

如你所见,CreationStrategy于建立对象时,会由Policies中取出『类型/id』对应的ICreationPolicy对象,接着利用 它来取得ConstructorInfo(构造函数方法),再以GetParameters方法来取得构造函数所需的参数,最后调用此构造函数。这段程序代码告诉我们Policy的真正用途,就是用来协助Strategy于不同『类型/id』对象建立时,采取不同的动作,这也就是说,Strategy与Policy通常是成对出现的。

  • Locator

最后一个尚未弄清楚的关键元素是Locator,我们于调用Builder的BuildUp方法时,建立了一个Locator对象并传入该方法,这是用来做什么的呢?在ObjectBuilder中,Locator扮演两种角色,第一个角色是提供一个对象容器供Strategy使用,这点可以透过以下程序了解。

public class SingletonStrategy : BuilderStrategy
{
public override object BuildUp(IBuilderContext context, Type typeToBuild,
object existing, string idToBuild)
    {
        DependencyResolutionLocatorKey key = new DependencyResolutionLocatorKey(typeToBuild, idToBuild);
if (context.Locator != null && context.Locator.Contains(key, SearchMode.Local))
        {
            TraceBuildUp(context, typeToBuild, idToBuild, “”);
return context.Locator.Get(key);
        }
return base.BuildUp(context, typeToBuild, existing, idToBuild);
    }
}

SingletonStrategy是一个用来维持某一个对象只能有一份实体存在,当此Strategy被唤起时,其会先至Locator寻找目前要求的对象是否已被建立,是的话就取出该对象并传回。Locator同时也可以作为一个Service Locator,这点可以由以下程序代码来验证。

locator.Add(“Test”,new TestObject());
………….
TestObject obj = locator.Get<TestObject>(“Test”);

当然,这种手法有一个问题,那就是TestObject对象是预先建立后放在Locator中,这并不是一个好的设计,后面的章节我们会提出将Service Locator与Dependency Injection整合的手法。

PS:ObjectBuidler的Locator离完善的Service Locator还有段距离。

四、Dependency Injection With ObjectBuilder

ObjectBuilder支持Dependency Injection中定义的三种Injection模式,本章将一一介绍如何运用ObjectBuilder来实现。

4-1、Constructor Injection

Constructor Injection的精神在于使用构造函数来进行注入动作,本节延用InputAccept的例子,程序11是改采ObjectBuilder进行Constructor Injection的例子。

程序11

using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using Microsoft.Practices.ObjectBuilder;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
namespace OB_ConstructorInjectionTest
{
class Program
    {
static void UseValueParameter(MyBuilderContext context)
        {
            ConstructorPolicy creationPolicy = new ConstructorPolicy();
            creationPolicy.AddParameter(new ValueParameter(typeof(IDataProcessor), new PromptDataProcessor()));
            context.Policies.Set<ICreationPolicy>(creationPolicy, typeof(InputAccept), null);
        }
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext(new Locator());
            context.InnerChain.Add(new CreationStrategy());
            UseValueParameter(context);
            InputAccept accept = (InputAccept)context.HeadOfChain.BuildUp(context, typeof(InputAccept), null, null);
            accept.Execute();
            Console.Read();
        }

    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
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
    }
}

程序于一开始时,建立了一个MyBuilderContext对象,会自行建立BuilderContext对象而不使用Builder对象的目的很单纯,就是为了厘清个别Strategy究竟做了那些事,这点在使用Builder对象时,会因为内建的Strategy都已加入,而显得有些模糊。在MyBuilderContext对象建立后,此处将一个CreationStrategy加到Strategy串行中,CreationStrategy这个Strategy被归类为Creation阶段,是真正建立对象的Strategy,紧接着UseValueParameter方法会被调用,这个方法中建立了一个ConstructorPolicy对象,并调用其AddParameter方法,加入一个ValueParameter对象,这个ValueParameter对象就对应着InputAccept的构造函数所需的参数,CreationStrategy于对象建立后,会透过BuilderContext的Policies来取得『类型/id』对应的ICreationPolicy对象(本例就是ConstructorPolicy对象),然后调用ICreationPolicy.SelectionConstructor方法,这个方法必须根据调用者已用ICreationPolicy.AddParameter所传入的参数来选择正确的构造函数,然后再调用这个构造函数并填入参数值来完成对象建立工作,图5是这整个流程的示意图。

图5

图中读者可能会有所迷惑的是,FormatterServices.GetSafeUninitializedObject方法是何作用?这是.NET Framework中一个建立对象的途径,与一般new或是Activator.CreateInstance方式不同,GetSafeUninitializedObject方法并不会触发该对象的构造函数,只是单纯的将对象建立起来而已,因此CreationStrategy才必须于最后调用对应的构造函数。

  • Understanding Parameter

程序11中使用了一个ValueParameter对象,要知道这个对象的作用,我们得先了解Parameter在ObjectBuilder中所代表的意义,在三种注入模式中,有一个共通的规则,就是需要有参数来注入,Constructor Injection是透过构造函数参数注入,而Interface Injection则是透过函数参数注入,Setter Injection则是透过属性注入,因此参数是这三种注入模式都会用到的观念,所以ObjectBuilder定义了IParameter接口,并提供一组实现此接口的参数对象,于注入时期由这些参数对象来取得参数值,如图6。

图6

  • ValueParameter

这是一个最简单的Paramter对象,构造函数如下所示:

public ValueParameter(Type valueType, object value)

它的GetValue方法仅是将构造函数传入的value对象传回而已。

  • DependencyParamter

DependencyParameter是一个功能强大的Parameter对象,程序12是以DependencyParameter来取代ValueParameter完成Constructor Injection的例子。

程序12

static void UseDependencyParameter(MyBuilderContext context)
{
   ConstructorPolicy creationPolicy = new ConstructorPolicy();
   creationPolicy.AddParameter(new DependencyParameter(typeof(IDataProcessor),null,
typeof(PromptDataProcessor),NotPresentBehavior.CreateNew,SearchMode.Local));
   context.Policies.Set<ICreationPolicy>(creationPolicy, typeof(InputAccept), null);
   ConstructorPolicy creationPolicy2 = new ConstructorPolicy();
   context.Policies.Set<ICreationPolicy>(creationPolicy2, typeof(PromptDataProcessor),null);
}

读者可以发现,DependencyParameter并未要求建构者传入任何对象实体,而是要求建构者传入注入时对应的参数类型、参数名称、实体类型、NotPersentBehavoir及SearchMode等参数,下面的程序行表是DependencyParameter的构造函数:

public DependencyParameter(Type parameterType, string name,
          Type createType, NotPresentBehavior notPresentBehavior, SearchMode searchMode)

第一个参数是参数的类型,第二个参数是参数的名称,当ConstructorPolicy于SelectConstructor方法时,会依据这两个参数来选取适合的构造函数,第三个参数是实体对象的类型,以本例来说,就是以PromptDataProcessor这个类型建立对象来传入需要IDataProcessor类型的构造函数、方法或属性,第四个参数则影响了DependencyParameter的取值动作,预设情况下,DependencyParameter会先至Locator中取值,这个动作会受到第五个参数:SearchMode的影响(稍后会介绍这一部份),如果找不到的话,就会依据此参数值来做动作,NotPersentBehavior这个列举的定义如下:

public enum NotPresentBehavior
{                   
   CreateNew,
   ReturnNull,
   Throw,
}

CreateNew代表着当DependencyParameter于Locator找不到需要的值时,调用BuilderContext.HeadOfChain.BuildUp方法来建立该对象,以此例来说即是如此,所建立对象

 

[转][黄忠成]Object Builder Application Block (1)

Filed under: C#,dotNet,IoC,Object Builder — systembug @ 3:02 上午

本文相关代码下载

[转]Object Builder Application Block

文/黄忠成 ;2006/9/21

原文链接:http://blog.csdn.net/Code6421/archive/2006/09/25/1282139.aspx

整理:吕震宇

一、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<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来取得。