A continuing series from the previous article here
The latest version of this code is now hosted at Codeplex
Editor Note 02/16/2008: The P&P team has released the first CTPs for DI container named Unity. This is not dissimilar to the approach that is discribed in this series of postings. We suggest that the code here is used only as a learning exercise for Object Builder and Unity itself is used for your applciations.
Singletons vs. Instances
So remember in the last article when I mentioned the joy of policies? Well now we're going to play with a new one to show you how to configure ObjectBuilder if you need only one instance of a concrete class. In this case you will use an instance of the ISingletonPolicy policy. Of course the Patterns & Practices team has been great and provided an implementation for use that works pretty darn well. In most cases you don't need to create a custom implementation (in a future posting I'll show you one spiffy version I use).
In order to use it simply add an instance of the policy mapped to the type you want it to apply to:
Builder builder = new Builder();
ISingletonPolicy policy = new SingletonPolicy(true);
builder.Policies.Set<ISingletonPolicy>(policy, typeof(MyObject), null);
note: the last parameter is the optional ID the policy applies to
Viola! That's it! Well, not quite. You see, since we're talking about singletons what we're really discussing here is maintaining object lifetime. There's two ways to do that. You can simply by holding onto a reference or you can delegate this to an 3rd party. Obviously this 3rd party is going to have to plug into Object Builder so what we need to provide here a container to manage this lifetime as a ILifetimeContainer. Once again, P&P has a pretty nifty default implementation you can use, the LifetimeContainer class
Now you can test this out like this:
using Microsoft.Practices.ObjectBuilder;
using NUnit.Framework;
Builder builder = new Builder();
Locator locator = new Locator();
locator.Add(typeof(ILifetimeContainer), new LifetimeContainer());
MyObject o1 = (MyObject)builder.BuildUp(locator, typeof(MyObject), null, null);
MyObject o2 = (MyObject)builder.BuildUp(locator, typeof(MyObject), null, null);
//Not Same
Assert.IsFalse(Object.ReferenceEquals(o1, o2));
ISingletonPolicy policy = new SingletonPolicy(true);
builder.Policies.Set<ISingletonPolicy>(policy, typeof(MyObject), null);
o1 = (MyObject)builder.BuildUp(locator, typeof(MyObject), null, null);
o2 = (MyObject)builder.BuildUp(locator, typeof(MyObject), null, null);
//Are Same
Assert.IsTrue(Object.ReferenceEquals(o1, o2));
Putting it back together with our existing code
using System;
using System.Collections.Generic;
using Microsoft.Practices.ObjectBuilder;
/// <summary>
/// Concrete implementation of <see cref="IServiceProvider"/> that provided access to the factory capabilities of Object Builder subsystem.
/// </summary>
/// <remarks>
/// <note type="implementnotes">
/// This type provides a simplistic implementation of a bridge to Object
/// Builder. Some features such as contructor literals and lifetime management
/// facilities are not utilized in this version. Type mapping, however, is
/// specifically supported via the <see cref="ObjectBuilderServiceLocator(IEnumerable{TypeMapping})"/>
/// constructor.
/// </note>
/// </remarks>
public class ObjectBuilderServiceLocator : IServiceProvider
{
#region Fields
private readonly Builder _builder;
private readonly Locator _locator;
private readonly LifetimeContainer _lifetime;
#endregion
#region Constructors
/// <summary>
/// Intiailizes a new instance of the <see cref="ObjectBuilderServiceLocator"/> class with the default configuration.
/// </summary>
public ObjectBuilderServiceLocator()
{
this._builder = new Builder();
this._locator = new Locator();
this._lifetime = new LifetimeContainer();
this._locator.Add(typeof(ILifetimeContainer), this._lifetime);
}
/// <summary>
/// Initializes a new instance of the <see cref="ObjectBuilderServiceLocator"/> class with the supplied type mappings.
/// </summary>
/// <remarks><paramref name="typeMappings"/> will ignore <see langkeyword="null">null</see> items</remarks>
/// <param name="typeMappings">The type mappings to create. This value may be <see langkeyword="null">null</see>. Any dictionary value that is <see langkeyword="null">null</see> will be skipped.</param>
public ObjectBuilderServiceLocator(IEnumerable<TypeMapping> typeMappings)
: this()
{
if (typeMappings == null) return;
foreach (TypeMapping typeToMap in typeMappings)
{
if (typeToMap == null) continue;
this.CreateTypeMapping(typeToMap);
}
}
#endregion
#region IServiceProvider Members
/// <summary>
/// Gets the service object of the specified type.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <returns>A service object of type <paramref name="serviceType"/><p>- or -</p><see langkeyword="null">null</see>
/// if there is no service object of type <paramref name="serviceType"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="serviceType"/> is <see langkeyword="null">null</see>.</exception>
/// <exception cref="ArgumentException"><paramref name="serviceType"/> is <see langkeyword="abstract">abstract</see><p>- or -</p>
/// an <see langkeyword="interface"/> that is not mapped.</exception>
public virtual Object GetService(Type serviceType)
{
if (serviceType == null) throw new ArgumentNullException("serviceType");
return this._builder.BuildUp(this._locator, serviceType, null, null);
}
#endregion
#region Methods
/// <summary>
/// Creates a mapping between two types, one the interface contract type, the other for the type to fulfill the contract.
/// </summary>
/// <param name="typeToMap">The mapping to perform.</param>
/// <exception cref="ArgumentNullException"><paramref name="typeToMap"/> is <see langkeyword="null">null</see>.</exception>
public void CreateTypeMapping(TypeMapping typeToMap)
{
if (typeToMap == null) throw new ArgumentNullException("typeToMap");
TypeMappingPolicy policy = new TypeMappingPolicy(typeToMap.ReificationType, null);
this._builder.Policies.Set<ITypeMappingPolicy>(policy, typeToMap.ContractType, null);
}
/// <summary>
/// Assigns the singleton pattern to <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type to be assigned the singleton pattern.</typeparam>
public void AssignSingleton<T>()
{
ISingletonPolicy policy = new SingletonPolicy(true);
this._builder.Policies.Set(policy, typeof(T), null);
}
public virtual void AssignSingleton<T>(T instance)
{
PolicyList policies = new PolicyList();
policies.SetDefault<ISingletonPolicy>(new SingletonPolicy(true));
this._builder.BuildUp<T>(this._locator, null, instance, policies);
}
#endregion
}
/// <summary>
/// Type mapping value object.
/// </summary>
public class TypeMapping : IEquatable<TypeMapping>
{
#region Fields
private readonly Type _contractType;
private readonly Type _reificationType;
#endregion
#region Properties
/// <summary>
/// Gets the contract type.
/// </summary>
/// <value>The contract type.</value>
public Type ContractType
{
get
{
return _contractType;
}
}
/// <summary>
/// Gets the type to contract reify.
/// </summary>
/// <value>The type to contract reify.</value>
public Type ReificationType
{
get
{
return _reificationType;
}
}
#endregion
#region Overrides
/// <summary>
/// Determines whether the specified <seealso cref="Object"/> is equal to the current <seealso cref="Object"/>.
/// </summary>
/// <param name="obj">The <see cref="Object"/> to compare with the current <seealso cref="Object"/>.</param>
/// <returns><see langkeyword="true">true</see> if the specified <seealso cref="Object"/> is equal to the current <seealso cref="Object"/>; otherwise, <see langkeyword="false">false</see>.</returns>
public override Boolean Equals(Object obj)
{
if (obj is TypeMapping) return Equals(obj as TypeMapping);
return base.Equals(obj);
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="TypeMapping"/> class.
/// </summary>
/// <param name="contractType">The type being mapped.</param>
/// <param name="reificationType">The type to map with.</param>
public TypeMapping(Type contractType, Type reificationType)
{
if (contractType == null) throw new ArgumentNullException("contractType");
if (reificationType == null) throw new ArgumentNullException("reificationType");
if (!contractType.IsAssignableFrom(reificationType))
throw new ArgumentException(
String.Format("Type '{0}' is not assignable from type '{1}'", contractType.FullName,
reificationType.FullName), "reificationType");
this._contractType = contractType;
this._reificationType = reificationType;
}
#endregion
#region IEquatable<TypeMapping> Members
/// <summary>
/// Determines whether the specified <seealso cref="TypeMapping"/> is equal to the current <seealso cref="TypeMapping"/>.
/// </summary>
/// <param name="obj">The <see cref="TypeMapping"/> to compare with the current <seealso cref="TypeMapping"/>.</param>
/// <returns><see langkeyword="true">true</see> if the specified <seealso cref="TypeMapping"/> is equal to the current <seealso cref="TypeMapping"/>; otherwise, <see langkeyword="false">false</see>.</returns>
public Boolean Equals(TypeMapping obj)
{
return (
obj != null &&
obj._contractType.Equals(_contractType) &&
obj._reificationType.Equals(_reificationType));
}
#endregion
}
And of course the test code...
using System;
using System.Collections.Generic;
using NUnit.Framework;
[TestFixture()]
public class TypeMappingTests
{
[Test()]
public void ConstructorSetsProperties()
{
Type contractType = typeof(Object);
Type reificationType = typeof(TypeMapping);
TypeMapping mapping = new TypeMapping(contractType, reificationType);
Assert.AreSame(contractType, mapping.ContractType);
Assert.AreSame(reificationType, mapping.ReificationType);
}
[Test()]
[ExpectedException(typeof(ArgumentNullException))]
public void ContractTypeMustNotBeNull()
{
Type contractType = null;
Type reificationType = typeof(TypeMapping);
new TypeMapping(contractType, reificationType);
}
[Test()]
[ExpectedException(typeof(ArgumentNullException))]
public void ReificationTypeMustNotBeNull()
{
Type contractType = typeof(Object);
Type reificationType = null;
new TypeMapping(contractType, reificationType);
}
[Test()]
[ExpectedException(typeof(ArgumentException))]
public void ReificationTypeMustBeAssignableToContractType()
{
Type contractType = typeof(TypeMapping);
Type reificationType = typeof(Object);
new TypeMapping(contractType, reificationType);
}
[Test()]
public void Equals()
{
TypeMapping map1 = new TypeMapping(typeof(Object), typeof(TypeMapping));
TypeMapping map2 = new TypeMapping(typeof(Object), typeof(TypeMapping));
TypeMapping map3 = new TypeMapping(typeof(Object), typeof(String));
TypeMapping map4 = new TypeMapping(typeof(IEquatable<TypeMapping>), typeof(TypeMapping));
Assert.IsFalse(map1.Equals(null));
Assert.IsTrue(map1.Equals(map2));
Assert.IsTrue(map1.Equals(map1));
Assert.IsFalse(map1.Equals(map3));
Assert.IsFalse(map1.Equals(map4));
}
}
[TestFixture()]
public class ObjectBuilderServiceLocatorTests
{
[Test()]
public void CanSupplyNullEnumerator()
{
ObjectBuilderServiceLocator locator = new ObjectBuilderServiceLocator(null);
locator.GetService(typeof(Object));
}
[Test()]
public void CanSupplyNullEnumeratorItem()
{
List<TypeMapping> maps = new List<TypeMapping>();
maps.Add(null);
ObjectBuilderServiceLocator locator = new ObjectBuilderServiceLocator(maps);
Object createdInstance = locator.GetService(typeof(Object));
Assert.IsNotNull(createdInstance);
}
[Test()]
public void TypeCanBeMappedInConstructor()
{
List<TypeMapping> maps = new List<TypeMapping>();
maps.Add(new TypeMapping(typeof(Object), typeof(ObjectBuilderServiceLocator)));
ObjectBuilderServiceLocator locator = new ObjectBuilderServiceLocator(maps);
Object createdInstance = locator.GetService(typeof(Object));
Assert.IsNotNull(createdInstance);
Assert.IsInstanceOfType(typeof(ObjectBuilderServiceLocator), createdInstance);
}
[Test()]
public void TypeCanBeMappedViaMethod()
{
TypeMapping mapping = new TypeMapping(typeof(Object), typeof(ObjectBuilderServiceLocator));
ObjectBuilderServiceLocator locator = new ObjectBuilderServiceLocator();
locator.CreateTypeMapping(mapping);
Object createdInstance = locator.GetService(typeof(Object));
Assert.IsNotNull(createdInstance);
Assert.IsInstanceOfType(typeof(ObjectBuilderServiceLocator), createdInstance);
}
[Test()]
public void CreatedTypesArePerInstance()
{
ObjectBuilderServiceLocator locator = new ObjectBuilderServiceLocator();
Object createdInstance1 = locator.GetService(typeof(Object));
Object createdInstance2 = locator.GetService(typeof(Object));
Assert.AreNotSame(createdInstance1, createdInstance2);
}
[Test()]
public void CanMarkSingleton()
{
ObjectBuilderServiceLocator locator = new ObjectBuilderServiceLocator();
locator.AssignSingleton<Object>();
Object createdInstance1 = locator.GetService(typeof(Object));
Object createdInstance2 = locator.GetService(typeof(Object));
Assert.AreSame(createdInstance1, createdInstance2);
}
[Test()]
public void CanSupplySingleton()
{
Object originalInstance = new Object();
ObjectBuilderServiceLocator locator = new ObjectBuilderServiceLocator();
locator.AssignSingleton(originalInstance);
Object createdInstance1 = locator.GetService(typeof(Object));
Object createdInstance2 = locator.GetService(typeof(Object));
Assert.AreSame(createdInstance1, createdInstance2);
Assert.AreSame(originalInstance, createdInstance1);
}
}
Enjoy your singletons...
Read part 4
|
|
Jimmy Zimms is currently debating if I Am Legend is worth seeing
|
|