IoC с зависимостями типа значения и типа объекта - PullRequest
8 голосов
/ 14 ноября 2011

Я ищу предложения относительно лучшего способа проектирования объектов для IoC

Предположим, у меня есть объект (Сервис), имеющий зависимость от DataContext, который зарегистрирован в Ioc.

Но для этого также требуется свойство name, я мог бы спроектировать объект так:

class Service
{
    public Service(IDataContext dataContext, 
        string name)
    {
        this._dataContext = dataContext;
        this._name = name
    }

    public string Name
    {
        get
        {
            return _name;
        }
    }
}

Проблема в том, что использование с контейнерами Ioc становится очень сложным, поскольку строковый объект, такой как имя, нелегко зарегистрировать, а использование становится сложнее с контейнером Ioc: Таким образом, разрешение становится запутанным:

var service = Ioc.Resolve<Service>( ?? )

Другой подход заключается в следующем:

class Service
{
   public Service(IDataContext dataContext)
   {
        this._dataContext = dataContext;
   }

    public string Name { get; set; }
} 

Разрешение теперь стало проще:

var service = Ioc.Resolve<Service>();
service.Name = "Some name";

Единственный недостаток - указание имени больше не требуется. Я хотел бы услышать от экспертов DI или IoC, как они пойдут на разработку этого и при этом будут оставаться совершенно независимыми от технологии контейнеров для конкретного Ioc.

Я знаю, что многое зависит от того, как вы хотите использовать это, вариант 2 был бы идеальным, если бы имя действительно было необязательным. Но в случае, когда требуется имя, вы можете добавить шаг проверки в другой точке кода, а вместо этого перейти к дизайну, чтобы упростить Ioc.

Мысли

Ответы [ 5 ]

4 голосов
/ 14 ноября 2011

Ваш выбор DI-контейнера не должен диктовать дизайн вашего API. Если name не является обязательным, оно должно быть частью подписи конструктора (что делает его обязательным).

Затем возникает следующий вопрос: как сконфигурировать контейнер, не неся при этом тонны накладных расходов. Как это сделать, зависит от контейнера. Вот как реализовать соглашение вокруг строкового аргумента в Castle Windsor:

public class NameConvention : ISubDependencyResolver
{
    public bool CanResolve(CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model, DependencyModel dependency)
    {
        return dependency.TargetType == typeof(string)
            && dependency.DependencyKey == "name";
    }

    public object Resolve(CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model, DependencyModel dependency)
    {
        return "foo"; // use whatever value you'd like,
                      // or derive it from the provided models
    }
}

Затем зарегистрируйте NameConvention в контейнере следующим образом:

container.Kernel.Resolver.AddSubResolver(new NameConvention());

Если ваш контейнер не имеет соответствующих точек расширения, выберите контейнер, который имеет.

3 голосов
/ 14 ноября 2011

Проблема в том, что его очень сложно использовать с контейнерами Ioc. как строковый объект, такой как имя, не легко зарегистрировать и использование усложняется с контейнером Ioc

Большинство хороших IoC-контейнеров предоставляют простые способы предоставления аргументов конструктора при настройке.

Ваш первый пример - внедрение в конструктор - обычно считается предпочтительным способом. Думайте о своем конструкторе как о контракте, которому нужно следовать, который, когда он удовлетворен, отображает действительный объект.

Ваш второй пример кода - внедрение свойства - обычно считается менее предпочтительным, чем внедрение конструктора. В любом случае, контейнеры IoC обычно дают вам возможность предоставлять значения для параметров конструктора или свойств при конфигурации, которые будут предоставляться каждый раз, когда вы запрашиваете у IoC создание вам этого объекта.

Я не уверен, какой контейнер IoC вы хотите использовать, но вот пример кода, используемого для настройки StructureMap и предоставления строковых значений для различных служб. Если я не читаю ваш вопрос, похоже, это то, что вы хотите сделать.

ObjectFactory.Initialize(x =>
{
    x.For<ICatalogAdminService>().Use<DLinkCatalogAdminService>()
        .Ctor<string>("catalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("webCatalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_WebConnectionString"].ConnectionString)
        .Ctor<string>("dlinkPromotionAdminConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
    x.For<IContentManagementAdminService>().Use<DLinkContentManagementAdminService>()
        .Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("webCatalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_WebConnectionString"].ConnectionString)
        .Ctor<string>("dlinkPromotionConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
    x.For<IPromotionAdminService>().Use<DLinkPromotionAdminService>()
        .Ctor<string>("catalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("promotionConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
    x.For<ISearchService>().Use<Extractor>();
    x.For<IImporter>().Use<Importer>();
    x.For<IOrderAdminService>().Use<DLinkOrderAdminService>()
        .Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("orderConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_OrdersConnectionString"].ConnectionString);
});

EDIT

Отвечая на комментарий, если вы на самом деле хотите указать аргумент конструктора вручную, он будет выглядеть так:

ObjectFactory.GetInstance<ICatalogAdminService>(new ExplicitArguments(
    new Dictionary<string, object>() { { "parameter1", "someValue" } }));

Очевидно, что это может быть ужасно быстро, так что вы можете использовать некоторые фабричные / вспомогательные методы, если вы обнаружите, что делаете это часто.

2 голосов
/ 14 ноября 2011

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

Другой вариант - использовать что-то вроде аннотации привязки.Я не знаю, какую платформу внедрения зависимостей вы используете, но вот как это можно сделать в guice (java) фреймворке, с которым я сейчас работаю.

1 голос
/ 15 ноября 2011

Проектируйте его так, как вы всегда делаете, помня о хороших инженерных практиках (SOLID и т. Д.).Тогда, если ваш контейнер выбора ограничивает вас, вы либо используете его неправильно, либо используете неправильный контейнер.

В случае Windsor вы можете легко предоставить встроенный, жестко закодированныйзависимости от компонентов во время регистрации:

container.Register(Component.For<Foo>().DependsOn(new{name = "Stefan"});

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

Если значениенеобязательно, затем сделайте его необязательным, либо с помощью конструктора, который указывает для него значение по умолчанию, либо с помощью перегрузок конструктора, либо в качестве свойства.Опять же, хороший контейнер будет обрабатывать все эти случаи, если тот, который вы сейчас используете, возможно, не переключится на лучший.

1 голос
/ 14 ноября 2011

Если вы используете Castle Windsor, вы можете использовать печатные фабрики, о которых вы можете прочитать здесь .По сути, типизированные фабрики позволяют вам создавать интерфейс, который выглядит следующим образом.

public interface IServiceFactory
{
    IService Create(string name);
}

Внедрение этого и вызов Create() с именем по вашему выбору Виндзор вернет созданную реализацию IService.

...