Обеспечение зависимости в IoC через конструктор? - PullRequest
9 голосов
/ 16 апреля 2009

Я пытаюсь смириться с использованием IoC / Dependency Injection, в то же время программируя контракты, а не конкретные классы. Дилемма, с которой я столкнулся, - это напряжение между:

  1. Выполнять программирование для интерфейсов для IoC : я начал с IoC, сильно полагаясь на интерфейсы. Судя по образцам проектов Spring, при программировании контракта с IoC лучше всего использовать интерфейсы.

  2. ( ... хотя абстрактные классы, как правило, предпочитают : , главный недостаток интерфейсов заключается в том, что они гораздо менее гибки, чем классы, когда речь идет об эволюции API * 1014. *)

  3. Делать зависимости класса явными через конструктор Я чувствую, что это хорошая практика программирования - передавать зависимости конструктору класса. Действительно, - это внедрение зависимостей.

  4. ... за исключением того, что вы не можете применить сигнатуру конструктора в интерфейсах / абстрактных предложениях : ни интерфейсы, ни абстрактные классы не позволяют определять сигнатуру конструктора (легко / элегантно) . См. Также Раздел 4.4 Руководства по проектированию платформы: НЕ определяйте открытые или защищенные внутренние конструкторы в абстрактных типах. ... Конструкторы должны быть открытыми, только если пользователям нужно будет создавать экземпляры типа.

Этот вопрос относится к предыдущему вопросу stackoverflow: Интерфейс, определяющий сигнатуру конструктора?

Но мой вопрос:

Поскольку вы не можете определить конструктор в интерфейсе / абстрактном классе C #, как показывает вопрос выше, на практическом уровне:

Как вы согласуете это с разумной практикой передачи зависимостей через конструктор ?

Редактировать: Спасибо за ответы. Я надеюсь на некоторое понимание того, что я должен сделать в этом случае. Просто не использовать конструктор аргументов? Использовать какой-то метод Init (), который принимает зависимости? Edit2: Спасибо за отличные ответы, очень полезно.

Ответы [ 3 ]

8 голосов
/ 17 апреля 2009

Я всегда думаю, что это легче объяснить с помощью (выдуманного) примера ...

Представьте, что у вас есть интерфейс ICustomerRepository, интерфейс IShoppingCartRepository и интерфейс ICheckout. У вас есть конкретные реализации этих интерфейсов - CustomerRepository, ShoppingCartRepository и CheckoutService.

Ваш конкретный класс CheckoutService имеет конструктор, который принимает ICustomerRepository и IShoppingCartRepository - например,

public CheckoutService(ICustomerRepository customerRepository, IShoppingCartRepository shoppingCartRepository)
{
  // Set fields for use in some methods later...
  _customerRepository = customerRepository;
  _shoppingCartRepository = shoppingCartRepository;
}

Затем, когда вы хотите, чтобы реализация ICheckoutService выполняла некоторую работу, вы сообщаете своему контейнеру IoC, какой конкретный класс он должен использовать для каждого типа интерфейса, и просите его создать вам ICheckoutService. Ваш контейнер IoC пойдет и построит ваши классы для вас, вставляя правильные конкретные классы в конструктор вашего CheckoutService. Здесь также будут построены зависимости на всем протяжении иерархии классов, поэтому, если, например, ваш ShoppingCartRepository использует интерфейс IDatabaseSession в конструкторе, ваш контейнер IoC также внедрит эту зависимость, если вы сказали ему, какой конкретный класс использовать для вашей службы IDatabaseService.

Вот код, который вы можете использовать при настройке (например) StructureMap в качестве контейнера IoC (этот код обычно вызывается во время запуска приложения):

public class AppRegistry : Registry
{
  public AppRegistry()
  {
    ForRequestedType<ICheckoutService>().TheDefaultIsConcreteType<CheckoutService>();
    ForRequestedType<ICustomerRepository>().TheDefaultIsConcreteType<CustomerRepository>();
    // etc...
  }
}

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

var checkoutService = ObjectFactory.GetInstance<ICheckoutService>();

Надеюсь, это имеет смысл!

1 голос
/ 16 апреля 2009

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

Конструктор - это деталь реализации, поэтому вам не нужно отделять его определение от конкретного класса.

0 голосов
/ 16 апреля 2009

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

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

Принцип IoC гласит, что вместо того, чтобы класс A знал и создавал экземпляр класса B, вы должны вместо этого передать ссылку на IB в конструктор A. Тогда A не нужно будет знать о классе B, и поэтому вы можете легко замените класс B некоторой другой реализацией IB.

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

...