альтернативы внедрения зависимостей

Я смотрю на внедрение зависимостей, вижу преимущества, но у меня проблемы с синтаксисом, который он создает. У меня есть этот пример

public class BusinessProducts
{
   IDataContext _dx;

   BusinessProducts(IDataContext dx)
   {
      _dx = dx;
   }

   public List<Product> GetProducts()
   {
    return dx.GetProducts();
   }
}

Проблема в том, что я не хочу писать

BusinessProducts bp = new BusinessProducts(dataContextImplementation);

Я бы продолжил писать

BusinessProducts bp = new BusinessProducts();

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

Есть ли какие-либо альтернативы этому подходу, поскольку я хотел бы сохранить свой оригинальный синтаксис для создания объектов, но я хотел бы по-прежнему иметь возможность имитировать зависимости при модульном тестировании, или это могут сделать для меня эти структуры внедрения зависимостей?

Я пишу в C #, но приветствуются альтернативы из других языков

11.12.2008 12:06:32
Хорошо, если вы используете ключевое слово «new» напрямую вместо запроса объекта, вы не используете внедрение зависимостей.
Pop Catalin 11.12.2008 12:19:14
@ Поп, ты думаешь о структуре DI, а не о DI. В DI паттерн, вставка экземпляра класса, от которого вы зависите в конструкторе, вполне разумна. Другой способ - сделать это через свойства.
tvanfosson 11.12.2008 12:37:06
@Pop Catalin - это пример DI. Экземпляр объекта, который нужно внедрить, - это «IDataContext», а не «BusinessProducts».
Owen 11.12.2008 12:37:51
@tvanfosson, Owen: в любом случае зависимость должна быть внедрена вам, если она существует, если нет, это не внедрение зависимости, это фабричный шаблон, если в будущем вы добавите новую зависимость в класс BusinessProducts, вам не следует возвращаться и изменять конструктор использовал, потому что ...
Pop Catalin 11.12.2008 15:27:16
Вы просто «объявили» новую зависимость для класса и состояний шаблона, о которых нужно позаботиться автоматически. Давайте не будем путать внедрение зависимостей с фабричным шаблоном, при использовании внедрения зависимостей все зависимости должны разрешаться автоматически, чего нельзя сделать при использовании «new».
Pop Catalin 11.12.2008 15:29:14
11 ОТВЕТОВ
РЕШЕНИЕ

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

Так что мой класс бизнес-продуктов будет выглядеть так:

public class BusinessProducts
{
     private IDataContextFactory DataContextFactory { get; set; }  // my interface

     public BusinessProducts() : this(null) {}

     public BusinessProducts( IDataContextFactory factory )
     {
          this.DataContext = factory ?? new BusinessProductsDataContextFactory();
     }

     public void DoSomething()
     {
          using (DataContext dc = this.DataContextFactory().CreateDataContext())
          {
             ...
          }
     }

Альтернативой этому было бы сделать свойство фабрики общедоступным и добавить альтернативную фабрику, установив свойство. В любом случае, если вы хотите сохранить нулевой конструктор, вам нужно будет указать значение по умолчанию.

9
24.11.2009 21:18:32

http://springframework.net/ и http://structuremap.sourceforge.net/Default.htm , вероятно, являются наиболее часто используемыми средами DI для языков, основанных на .NET, и они будут делать то, что вам нужно.

1
11.12.2008 12:10:37

Здесь есть два разных случая:

В производственном коде вы никогда не напишите

new BusinessProducts(dataContextImplementation)

потому что внедрение зависимостей обычно создает полную иерархию объектов для вас. Это «вирусная» природа шаблонов внедрения зависимостей, они, как правило, принимают полный контроль над созданием вашего сервиса.

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

3
11.12.2008 12:15:25
@krosenvold - я думаю, что вы думаете о DI-структурах, а не DI-шаблоне. Шаблон просто говорит, что вы делаете именно это - вводите зависимости через конструктор или установщики свойств. Вы можете использовать DI шаблон без использования DI-фреймворка.
tvanfosson 11.12.2008 12:40:50

Вы также можете посмотреть на Виндзор для IoC.

0
11.12.2008 12:16:18

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

6
11.12.2008 12:18:44

Как правило, сама структура будет иметь логику для построения всего дерева объектов. Например, вместо

new SomeObjectO(diContext)

вы бы назвали структуру следующим образом:

DIFramework.GetNew<SomeObjectO>();

или

DIFramework.Get<SomeObject>();

Еще одна интересная структура, на которую стоит обратить внимание, если вы хотите узнать о DI и процессе, - это проекты Microsoft Unity и Object Builder.

1
11.12.2008 12:28:37
Почему почти все, кто отвечает, путают DI шаблон с DI-фреймворком? Каркасы DI используют DI паттерн, но вам не нужно использовать каркас DI просто для реализации DI паттерна.
tvanfosson 11.12.2008 12:43:39
Я согласен с вами, tvanfosson. Этот ответ только помог мне понять, что DI Framework делает для вас
terjetyl 11.12.2008 12:47:17
Не говорить, что это бесполезно (для вас), но говорить о двух, как будто они одно и то же, просто сбивает с толку.
tvanfosson 11.12.2008 12:49:42
@tvanfosson: Точка занята. Тем не менее, я иллюстрировал, как структура может реализовать шаблон. TT, хотя и ссылался на шаблон DI, спрашивал об интерфейсе и о том, как можно реализовать шаблон DI. Вышеуказанные фабричные методы являются одним из способов.
Szymon Rozga 11.12.2008 14:09:51
Они, безусловно, используются взаимозаменяемо. Фаулер говорит, что IoC является слишком общим термином, который предпочитает использовать Dependency Injection. Узнайте больше здесь: martinfowler.com/articles/injection.html
Szymon Rozga 11.12.2008 14:35:58

У меня обычно был бы пустой конструктор, который использует твердый экземпляр (или экземпляры, созданные IoC), и один с DI. т.е.

public class BusinessProducts
{
   IDataContext _dx;

   BusinessProducts()
   {
      _dx = new SolidDataContext();
   }

   BusinessProducts(IDataContext dx)
   {
      _dx = dx;
   }
}

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

6
11.12.2008 12:36:10
Я несколько раз использовал эту технику для инъекций в различных реализациях с целью модульного тестирования. Это полезно, если у вас нет контейнера DI.
RichardOD 12.05.2009 13:45:13

Если вам действительно не нравится внедрять этот экземпляр в конструктор, вы можете попробовать использовать CommonServiceLocator с вашей любимой совместимой платформой внедрения зависимостей .NET. Это позволит вам написать код так:

public class BusinessProducts
{
   IDataContext _dx;

   BusinessProducts()
   {
      _dx = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IDataContext>();
   }

   public List<Product> GetProducts()
   {
    return dx.GetProducts();
   }
}

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

BusinessProducts bp = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<BusinessProducts>();

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

1
11.12.2008 12:55:31

Есть методика под названием DI бедного человека, которая выглядит следующим образом

public class BusinessProducts
{
   IDataContext _dx;

   BusinessProducts() : this(new DataContext()) {}

   BusinessProducts(IDataContext dx)
   {
      _dx = dx;
   }

   public List<Product> GetProducts()
   {
    return dx.GetProducts();
   }
}

Это не идеальный вариант, поскольку он связывает вас с реализацией, но является хорошей ступенькой к разъединенному коду. это похоже на @tvanfosson, но намного проще.

Я вторая рекомендация для Виндзора

1
11.12.2008 14:07:32

Мой код будет ссылаться на Microsoft Unity, но я уверен, что он довольно применим ко всем структурам DI. Если вы используете DI правильно, вам никогда не нужно вызывать новый BusinessObject (новый dataContext), ассоциация DI будет обрабатывать все это за вас.

Мой пример будет немного длиннее, поскольку я вставлю код, который я использую для запуска сайта Model View Presenter, полностью загруженного Unity. (Если вы хотите получить полный исходный код, посмотрите мой блог и скачайте его с моего SVN-сервера Assembla)

Загрузите контейнер (может быть в коде, как я предпочитаю или используя конфигурацию)

protected void Application_Start(object sender, EventArgs e)
{
    Application.GetContainer()
        // presenters / controllers are per request                 
        .RegisterType<IEmployeeController, EmployeeController>(new ContextLifetimeManager<IEmployeeController>())

        //Data Providers are Per session                
        .RegisterType<IEmployeeDataProvider, EmployeeDataProvider>(new SessionLifetimeManager<IEmployeeDataProvider>())

        //Session Factory is life time
        .RegisterType<INHibernateSessionManager, NHibernateSessionManager>(new ContainerControlledLifetimeManager());
}

Пользовательский модуль HTTP вызывает метод Unity BuildUp для каждой страницы во время вызова OnPreRequest.

private static void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
    var handler = HttpContext.Current.Handler;
    HttpContext.Current.Application.GetContainer().BuildUp(handler.GetType(), handler);

    // User Controls are ready to be built up after the page initialization is complete
    var page = HttpContext.Current.Handler as Page;
    if (page != null)
    {
        page.InitComplete += OnPageInitComplete;
    }
}

Ведущий контейнера страницы, украшенный атрибутом [Dependency]

public partial class Employees : Page, IEmployeeView
{
    private EmployeePresenter _presenter;

    [Dependency]
    public EmployeePresenter Presenter
    {
        set
        {
            _presenter = value;
            _presenter.View = this;
        }
    }
}

Ведущий с методом InjectionConstructor

public class EmployeePresenter : Presenter<IEmployeeView>
{
    private readonly IEmployeeController _controller;

    [InjectionConstructor]
    }
    public EmployeePresenter(IEmployeeController controller)
    {
        _controller = controller;
}

Контроллер следует примеру

public class EmployeeController : IEmployeeController
{
    private readonly IEmployeeDataProvider _provider;

    [InjectionConstructor]
    public EmployeeController(IEmployeeDataProvider DataProvider)
    {
        _provider = DataProvider;
    }
}

То же самое с провайдером

public class EmployeeController : IEmployeeController
{
    private readonly IEmployeeDataProvider _provider;

    [InjectionConstructor]
    public EmployeeController(IEmployeeDataProvider DataProvider)
    {
        _provider = DataProvider;
    }
}

Наконец, менеджер сеансов, который содержит только обычный конструктор.

public class NHibernateSessionManager : INHibernateSessionManager
{   
    private readonly ISessionFactory _sessionFactory;

    public NHibernateSessionManager()
    {            
        _sessionFactory = GetSessionFactory();
    }
}

Итак, что происходит, когда запускается запрос страницы, метод BuildUp () вызывается на странице HttpModule. Затем Unity увидит Свойство, помеченное атрибутом Dependency, и проверит его контейнер, чтобы увидеть, существует ли внутри него объект EmployeePresenter.

Поскольку в контейнере такого объекта нет, он попытается создать EmployeePresenter. При проверке для создания класса, который он видит внутри Presenter, ему требуется конструктор, которому требуется вставленный в него IEmployeeController. Поскольку у контейнера действительно есть диспетчер для контроллера, он увидит, существует ли его экземпляр в контейнере, которого в начале запроса страницы не существует, поэтому он перейдет к созданию экземпляра контроллера.

Затем Unity увидит, что контроллеру требуется вставленный в него IEmployeeDataProvider, и он продолжит этот процесс, пока, наконец, не достигнет точки, когда провайдеру понадобится внедрить менеджер сеансов. Поскольку диспетчер сеансов больше не нуждается в внедрении, Unity затем создаст экземпляр диспетчера сеансов, сохранит его в контейнере, для которого ему задан ContainerLifeTimeManager, внедрит его в провайдера и сохранит этот экземпляр, и так далее, где он завершил создание Зависимость EmployeePresenter для страницы.

1
11.12.2008 14:21:45

Ваши чувства, хотя и действительны, неуместны.

Injection Dependency модель представляет собой прямое применение Инверсия управления принципе.

Это означает, что вместо того, чтобы ваш класс контролировал экземпляры других классов, которые он потребляет, это отношение инвертируется, и ему предоставляются зависимости.

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

4
11.12.2008 15:17:41