альтернативы внедрения зависимостей - PullRequest
14 голосов
/ 11 декабря 2008

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

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 ]

9 голосов
/ 11 декабря 2008

Я использую фабрику для своего контекста и внедряю ее, предоставляя подходящее значение по умолчанию, если предоставленная фабрика равна нулю. Я делаю это по двум причинам. Во-первых, я использую контекст данных в качестве объекта с областью действия, поэтому мне нужно иметь возможность создавать их при необходимости, а не держать их рядом. Во-вторых, я в основном использую 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())
          {
             ...
          }
     }

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

6 голосов
/ 11 декабря 2008

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

public class BusinessProducts
{
   IDataContext _dx;

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

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

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

6 голосов
/ 11 декабря 2008

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

4 голосов
/ 11 декабря 2008

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

Шаблон Внедрение зависимостей является прямым применением принципа Инверсия управления .

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

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

3 голосов
/ 11 декабря 2008

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

В рабочем коде вы будете никогда писать

new BusinessProducts(dataContextImplementation)

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

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

1 голос
/ 11 декабря 2008

Мой код будет ссылаться на 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 декабря 2008

Существует методика, называемая 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 декабря 2008

Если вам действительно не нравится внедрять этот экземпляр в конструктор, вы можете попробовать использовать 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 декабря 2008

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

new SomeObjectO(diContext)

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

DIFramework.GetNew<SomeObjectO>();

или

DIFramework.Get<SomeObject>();

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

1 голос
/ 11 декабря 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...