Подтвердить что ты не робот

Несколько реализаций для одного интерфейса с DI

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

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

Два монстра, оба реализующие интерфейс 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 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 работает совершенно неправильно? Как бы вы решили эту проблему?

4b9b3361

Ответ 1

Ваша инверсия контейнера управления не является factory как таковой. Ваш случай идеально подходит для шаблона factory.

Создайте новый абстрактный factory, который используется для создания ваших монстров:

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

И затем зарегистрируйте его реализацию в Autofac.

Наконец, используйте factory в своем классе:

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

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

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

Но в этом случае я не понимаю, почему вы не можете создавать реализации непосредственно в factory? Для factory:

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

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

Это не сложнее.

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

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

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

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

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

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

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

Резюме

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

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