0 Comments

A few days ago a collegaue of mine blogged about performing DI on the cheap. His post got me to thinking about the various IoC containers and DI frameworks that have sprung up (no pun intended, sorry for that heinous attempt at geek humor). As I've been working through the Social Timeline architecture I've concluded the importance that'll reside on being able to snap in time line data providers with ease. Inspired by Bo's post, I've named the project El Cheapo. Though it doesn't make use of any of the popular IoC/DI frameworks out there it does borrow some of the general ideas from their implementations. I was specifically inspired by Nikola Malovic's discussion of Unity, namely the way interfaces are resolved to types. 

As you'll see, the only significant difference between a typical service container implementation and this custom version is that this implementation takes into account the concern of having multiple implementations of a given interface. In Social Timeline for instance, I've listed the various time line data providers as tabs in the GUI layer. In this way a user can see all that they could represent on the timeline below the tabs. I had intended to use the Unity framework in this implementation but decided it was a little more lightweight to just build what I needed myself. Below, you'll see the code for the El Cheapo service container in its [current] entirety.

 

public class FunctionalityBasket
{
    Dictionary<object, Dictionary<string, object>> _innerBasket;
    public FunctionalityBasket()
    {
        _innerBasket = new Dictionary<object, Dictionary<string, object>>();
    }
    public void Register<I>(I implementation)
    {
        Register<I>(implementation, implementation.GetType().Name);
    }
    public void Register<I>(I implementation, string name)
    {
        Type t = typeof(I);
        if (!_innerBasket.ContainsKey(t))
            _innerBasket[t] = new Dictionary<string, object>();
        _innerBasket[t].Add(
            name,
            implementation
            );
    }
    public Dictionary<string, TInterface> GetImplementations<TInterface>()
    {
        Type t = typeof(TInterface);
        Dictionary<string, TInterface> ret = new Dictionary<string,TInterface>();
        Dictionary<string, object>.Enumerator enm = _innerBasket[t].GetEnumerator();
        while (enm.MoveNext())
            ret.Add(enm.Current.Key, (TInterface)enm.Current.Value);
        return ret;
    }
    public TInterface GetImplementation<TInterface>(string name)
    {
        return GetImplementations<TInterface>()[name];
    }
}

 

I feel the best way to exemplify usage of the FunBasket (another bad pun) is via a series of unit tests, which are below.

 

[TestFixture]
public class ElCheapoUnitTests
{
    [TestFixtureSetUp]
    public void SetupTestFixture()
    {
    }
    [TestFixtureTearDown]
    public void TearDownTestFixture()
    {
    }
    [Test]
    public void InterfacesAndImplementationsCanBeAddedToBasket()
    {
        FunctionalityBasket basket = new FunctionalityBasket();
        basket.Register<ILogger>(new NullLogger());
    }
    [Test]
    public void InterfacesAndImplementationsCanBeAddedAndListOfInterfacesRetrieved()
    {
        FunctionalityBasket basket = new FunctionalityBasket();
        basket.Register<ILogger>(new NullLogger(), NullLogger.Name);
        basket.Register<ILogger>(new LameLogger(), LameLogger.Name);
        Dictionary<string, ILogger> loggers = basket.GetImplementations<ILogger>();
        Assert.That(loggers.Count > 0, "Implementations can be added and retrieved by interface type.");
    }
    [Test]
    public void ReturnedImplementationsAreUsefulBasedOnInterface()
    {
        FunctionalityBasket basket = new FunctionalityBasket();
        basket.Register<ILogger>(new NullLogger(), NullLogger.Name);
        basket.Register<ILogger>(new LameLogger(), LameLogger.Name);
        Dictionary<string, ILogger> loggers = basket.GetImplementations<ILogger>();
        Assert.That(loggers.Count > 0, "Implementations can be added and retrieved by interface type.");
        foreach (ILogger logger in loggers.Values)
            logger.Log();
    }
    [Test]
    public void ImplementationsCanBeRetrievedByName()
    {
        FunctionalityBasket basket = new FunctionalityBasket();
        basket.Register<ILogger>(new NullLogger(), NullLogger.Name);
        basket.Register<ILogger>(new LameLogger(), LameLogger.Name);
        Assert.IsNotNull(basket.GetImplementation<ILogger>(NullLogger.Name), "Null Logger was added and is retrievable");
        Assert.IsNotNull(basket.GetImplementation<ILogger>(LameLogger.Name), "Lame Logger was added and is retrievable");
        basket.GetImplementation<ILogger>(NullLogger.Name).Log();
        basket.GetImplementation<ILogger>(LameLogger.Name).Log();
    }
}
// -------------------------------------------------------
// test implementations
// -------------------------------------------------------
interface ILogger
{
    void Log();
}
class NullLogger : ILogger
{
    public static string Name = "NullLogger";
    public void Log()
    {
        Console.WriteLine("This isn't happening");
    }
}
class LameLogger : ILogger
{
    public static string Name = "LameLogger";
    public void Log()
    {
        Console.WriteLine("I'm so lame");
    }
}

 

Happy Coding! And GO Bulldogs and Panthers this weekend!

Post comment