sobota, 6 kwietnia 2013

[IoC] Kontenery IoC

Kontenerem IoC można w skrócie nazwać pewien mechanizm wykonujący wstrzykiwanie zależności (Dependency Injection). Funkcjonalnością kontenera musi być możliwość konfiguracji zależności jakie zachodzą między obiektami. Kiedy prosimy kontener o typ podając mu na przykład interfejs, wie co ma nam zwrócić, a także wie jakie zależności powinien dany obiekt posiadać (czym różni się od wzorca ServiceLocatora).

Przykład zależności pomiędzy obiektami:

public class AudiA4
    {
        private readonly IEngine _engine;

        public AudiA4(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Run();
        }
    }

    public interface IEngine
    {
        void Run();
    }

    public class DieselEngine : IEngine
    {
        public void Run()
        {
            Console.Write("Diesel engine running...");
        }
    }

Kontener tworzą dwie metody. Pierwsza pozwala na rejestrowanie typów w słowniku. Typy rejestrowane powinny być po interfejsach tylko wtedy jeżeli implementujemy dany interfejs więcej niż raz. W przeciwnym wypadku tworzymy niepotrzebne byty. Druga metoda tworzy obiekt na podstawie wcześniej skonfigurowanych zależności. W C# możemy skorzystać z mechanizmu Reflection, przez co zajmuje nam to bardzo mało kodu. Ponieważ w bardziej rozbudowany aplikacjach możemy mieć wielopoziomowe grafy obiektów, metodę Resolve wołamy rekurencyjnie, aby rozwiązać wszystkie zależności.


public class IocContainer
{
    private Dictionary<Type, Type> dependencyMap;

    public IocContainer()
    {
        dependencyMap = new Dictionary<Type, Type>();
    }

    public T Resolve<T>()
    {
        return (T) Resolve(typeof(T));
    }

    private object Resolve(Type type)
    {
        Type resolvedType = null;
        try
        {
            resolvedType = dependencyMap[type];
        }
        catch
        {
            throw new KeyNotFoundException(String.Format("Cannot resolve type {0}", type.FullName));
        }
        var ctor = resolvedType.GetConstructors().First();
        var parameters = ctor.GetParameters();
        if (parameters.Count() == 0)
            return Activator.CreateInstance(resolvedType);
        IList<object> @params = new List<object>();
        foreach (var parameter in parameters)
        {
            @params.Add(Resolve(parameter.ParameterType));
        }
        return ctor.Invoke(@params.ToArray());
    }

    public void Register<TFrom, TTo>()
    {
        dependencyMap.Add(typeof(TFrom), typeof(TTo));
    }
}

Przykład użycia kontenera:

static void Main(string[] args)
{
    var container = new IocContainer();
    container.Register<AudiA4, AudiA4>();
    //zależnie od konfiguracji
    container.Register<IEngine, DieselEngine>();
    var car = container.Resolve<AudiA4>();
    car.Run();
}

Brak komentarzy:

Prześlij komentarz