2013-04-11 3 views
19

Сейчас я пытаюсь научить меня шаблону зависимостей инъекции с контейнером IOC от Autofac. Я привел очень простой пример, который представлен ниже. Хотя пример прост, я не могу заставить его работать должным образом.Несколько реализаций для одного интерфейса с DI

Вот мои классы/интерфейсы:

Два монстров, как реализующий интерфейс IMonster:

interface IMonster 
{ 
    void IntroduceYourself(); 
} 

class Vampire : IMonster 
{ 
    public delegate Vampire Factory(int age); 

    int mAge; 

    public Vampire(int age) 
    { 
    mAge = age; 
    } 

    public void IntroduceYourself() 
    { 
    Console.WriteLine("Hi, I'm a " + mAge + " years old vampire!"); 
    } 
} 

class Zombie : IMonster 
{ 
    public delegate Zombie Factory(string name); 

    string mName; 

    public Zombie(string name) 
    { 
    mName = name; 
    } 

    public void IntroduceYourself() 
    { 
    Console.WriteLine("Hi, I'm " + mName + " the zombie!"); 
    } 
} 

Тогда есть мое кладбище:

interface ILocation 
{ 
    void PresentLocalCreeps(); 
} 

class Graveyard : ILocation 
{ 
    Func<int, IMonster> mVampireFactory; 
    Func<string, IMonster> mZombieFactory; 

    public Graveyard(Func<int, IMonster> vampireFactory, Func<string, IMonster> zombieFactory) 
    { 
    mVampireFactory = vampireFactory; 
    mZombieFactory = zombieFactory; 
    } 

    public void PresentLocalCreeps() 
    { 
    var vampire = mVampireFactory.Invoke(300); 
    vampire.IntroduceYourself(); 

    var zombie = mZombieFactory.Invoke("Rob"); 
    zombie.IntroduceYourself(); 
    } 
} 

И, наконец, мой главный:

static void Main(string[] args) 
{ 
    // Setup Autofac 
    var builder = new ContainerBuilder(); 
    builder.RegisterType<Graveyard>().As<ILocation>(); 
    builder.RegisterType<Vampire>().As<IMonster>(); 
    builder.RegisterType<Zombie>().As<IMonster>(); 
    var container = builder.Build(); 

    // It's midnight! 
    var location = container.Resolve<ILocation>(); 
    location.PresentLocalCreeps(); 

    // Waiting for dawn to break... 
    Console.ReadLine(); 
    container.Dispose(); 
} 

И это моя проблема: Во время выполнения, Autofac бросает исключение на этой линии:

var vampire = mVampireFactory.Invoke(300); 

Кажется, что mVampireFactory на самом деле пытается создать экземпляр зомби. Конечно, это не сработает, так как конструктор зомби не возьмет int.

Есть ли простой способ исправить это? Или я понял, что Autofac работает совершенно неправильно? Как бы вы решили эту проблему?

+1

Я думаю, что вы можете искать [Именованные службы] (https://code.google.com/p/autofac/wiki/TypedNamedAndKeyedServices). –

+0

Как autofac разрешает два аргумента конструктора классу кладбища? – MattDavey

+1

У MattDavey есть правильный ответ. Похоже, что резольвер не может найти особенности Func и Func для обоих конструкторов, заменив их нулевым. Возможно, регистрация функции func в преобразователе может исправить проблему. – Fendy

ответ

22

Ваша инверсия контрольного контейнера не является заводом как таковым. Ваш корпус идеально подходит для заводского рисунка.

Создать новую абстрактную фабрику, которая используется для создания монстров:

public interface IMonsterFactory 
{ 
    Zombie CreateZombie(string name); 
    Vampire CreateVampire(int age); 
} 

А затем зарегистрировать его реализацию в Autofac.

Наконец использовать завод в своем классе:

class Graveyard : ILocation 
{ 
    IMonsterFactory _monsterFactory; 

    public Graveyard(IMonsterFactory factory) 
    { 
    _monsterFactory = factory; 
    } 

    public void PresentLocalCreeps() 
    { 
    var vampire = _monsterFactory.CreateVampire(300); 
    vampire.IntroduceYourself(); 

    var zombie = _monsterFactory.CreateZombie("Rob"); 
    zombie.IntroduceYourself(); 
    } 
} 

Вы можете, конечно, использовать специальные фабрики монстра тоже, если вы хотите. Тем не менее, используя интерфейсы, imho сделает ваш код намного читабельнее.

Update

Но как бы я реализовать завод? С одной стороны, фабрика не должна использовать контейнер IOC для создания монстров, потому что это считается злом (ухудшает шаблон DI для анти-шаблона локатора сервиса).

Я так устал слышать, что SL - это анти-шаблон. Это не. Как и во всех моделях, если вы используете его неправильно, это даст вам недостаток. Это относится ко всем образцам. http://blog.gauffin.org/2012/09/service-locator-is-not-an-anti-pattern/

Но в этом случае я не понимаю, почему вы не можете создавать реализации непосредственно на своей фабрике? Это то, что завод за:

public class PreferZombiesMonsterFactory : IMonsterFactory 
{ 
    public Zombie CreateZombie(string name) 
    { 
     return new SuperAwesomeZombie(name); 
    } 

    public Vampire CreateVampire(int age) 
    { 
     return new BooringVampire(age); 
    } 
} 

Это не сложнее, чем это.

С другой стороны, фабрика не должна создавать самих монстров, поскольку она будет обходить контейнер IOC и плотно соединяться с фабрикой и монстрами. Или снова я ошибаюсь? ;-)

Не имеет значения, что завод тесно связан с монстрами. Потому что это цель фабрики: абстрагироваться от создания объекта, чтобы в вашем коде ничего не было известно о бетонах.

Вы можете создать SuperDeluxeMonsterFactory, MonstersForCheapNonPayingUsersFactory и т. Д. Все остальные коды в вашем приложении не будут знать, что вы используете разные монстры (используя разные фабрики).

Каждый раз, когда вам приходится менять бетоны, вы либо переключаетесь на завод, либо просто изменяете существующий завод. Никакой другой код не будет затронут, пока ваши реализации монстров не будут нарушать Принцип замещения Лискова.

Factory против IoC контейнера

Так что разница между фабрикой и контейнером IoC тогда? IoC отлично подходит для разрешения зависимостей для ваших классов и поддержания сроков службы (контейнер может, например, автоматически удалять все одноразовые устройства при завершении HTTP-запроса).

Завод, с другой стороны, выделяется при создании объектов для вас. Он делает это и ничего больше.

Резюме

Так что, если вы где-то в коде должны получить конкретный тип реализации вы обычно должны использовать фабрику. Сама фабрика МОЖЕТ использовать IoC как локатор службы внутри (для разрешения зависимостей). Это нормально, так как это детализация на заводе, которые не влияют ни на что другое в вашем приложении.

Используйте контейнер IoC (через инъекцию зависимостей), если вы хотите разрешить службу (и не заботятся о том, какую реализацию вы получаете, или если вы получаете ранее созданный экземпляр).

+0

Но как я могу реализовать завод? С одной стороны, фабрика не должна использовать контейнер IOC для создания монстров, потому что это считается злом (ухудшает шаблон DI для анти-шаблона локатора сервиса). С другой стороны, фабрика не должна создавать самих монстров, потому что это обойдет контейнер IOC и плотно соединит завод и монстров. Или снова я ошибаюсь? ;-) – Boris

+0

Прочитайте мое обновление. – jgauffin

+0

Небольшое описание LSP: http://blog.gauffin.org/2011/05/liskovs-substitution-principle/ – jgauffin