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

Как реализовать IOC без глобальной статической службы (решение для не-сервисного локатора)?

мы хотим использовать Unity для IOC. Все, что я видел, это реализация, в которой есть одна глобальная статическая служба (позвольте ей назвать IOCService), которая содержит ссылку на контейнер Unity, который регистрирует все комбинации интерфейса/класса, и каждый класс запрашивает этот объект: дайте мне реализацию для Ithis или IThat.

Часто я вижу ответ, что этот шаблон не подходит, потому что он приводит к зависимости от ВСЕХ классов к IOCService (а не к контейнеру Unity, потому что он известен только внутри IOCService).

Но то, что я часто не вижу, - это: какой альтернативный способ?

Мишель

EDIT: выяснили, что глобальная статическая служба называется локатором службы, добавила ее в заголовок.

4b9b3361

Ответ 1

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

Например, основной метод большинства исполняемых файлов выглядит так (минус обработка исключений):

private static void main(string[] args) {

     Container container = new Container();

     // Configure the container - by hand or via file

     IProgramLogic logic = container.Resolve<IProgramLogic>();

     logic.Run();
}

Ваша программа (представленная здесь экземпляром IProgramLogic) не должна ничего знать о вашем контейнере, потому что container.Resolve создаст все свои зависимости - и зависимостей зависимостей, вплоть до классов листа без зависимостей.


ASP.NET - более сложный случай, потому что веб-формы не поддерживают инъекцию конструктора. Обычно я использую Model-View-Presenter в своих приложениях для веб-форм, поэтому мои классы Page действительно имеют только одну зависимость каждый - на своем ведущем. Я не unit test их (все интересное и проверяемое в моих презентаторах, которое я тестирую), и я никогда не подменяю докладчиков. Поэтому я не борюсь с фреймворком - я просто раскрываю свойство контейнера в своем классе HttpApplication (в global.asax.cs) и использую его непосредственно из файлов Page:

protected void Page_Load(object sender, EventArgs args) {
    ICustomerPresenter presenter = Global.Container.Resolve<ICustomerPresenter>();
    presenter.Load();
}

Конечно, этот локатор сервисов - хотя классы Page - единственное, что связано с локатором: ваш ведущий и все его зависимости все еще полностью отделены от вашей реализации контейнера IoC.

Если у вас много зависимостей в ваших файлах Page (то есть, если вы не используете Model-View-Presenter), или если вам важно отделить ваши классы Page от вашего Global класс приложения, вы должны попытаться найти фреймворк, который интегрируется в конвейер запросов веб-форм и использует инъекцию свойств (как предложено Николасом в комментариях ниже) - или напишите свой собственный IHttpModule и выполните сами инъекции свойств.

Ответ 2

+1 для зная, что Service Locator - плохая вещь.

Проблема заключается в том, что Unity не очень утончен, поэтому я не знаю, как легко/сложно сделать IoC правильным способом.

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

Ответ 3

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

В большинстве контейнеров вы помещаете ISomething[] в свой конструктор и введете все экземпляры ISomething в свой класс.

Таким образом, когда вы загружаете свое приложение:

  • Создайте экземпляр контейнера
  • Зарегистрируйте все свои лакомства
  • Разрешить основные классы (это потребует все другие зависимости, которые вам нужны)
  • Запустите "основную" часть приложения

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

Для веб-приложений ASP.NET вы, вероятно, в конечном итоге сохраните контейнер в состоянии приложения. Для приложений ASP.NET MVC вам необходимо изменить контроллер Factory.

Для настольных приложений все усложняется. Caliburn использует интересное решение этой проблемы, используя IResult (это для приложений WPF, но также может быть адаптировано для Windows Forms.

Ответ 4

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

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

Тривиальный случай достаточно прост. Если PaymentService зависит от IAccount, последнее должно быть введено IoC:

interface IAccount {
  Deposit(int amount);
}

interface CreditCardAccount : IAccount {
  void Deposit(int amount) {/*implementation*/}
  int CheckBalance() {/*implementation*/}
}

class PaymentService {

  IAccount account;

  public PaymentService (IAccount account) {
    this.account = account;
  }

  public void ProcessPayment() {
    account.Deposit(5);
  }
}
//Registration looks something like this
container.RegisterType<IAccount, CreditCardAccount>();
container.RegisterType<PaymentService>();

Не так тривиальный случай - это то, где вы хотите ввести несколько регистраций. Эта особенность применяется, когда вы выполняете какую-либо Converntion Over Configuration и создаете объект из имени.

В нашем примере оплаты, скажем, вы хотите перечислить все учетные записи и проверить их остатки:

class PaymentService {

  IEnumerable<IAccount> accounts;

  public PaymentService (IEnumerable<IAccount> accounts) {
    this.accounts = accounts;
  }

  public void ProcessPayment() {
    foreach(var account in accounts) {
      account.Chackbalance();
    }
  }
}

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

Если вам все равно, что эти объекты живут вечно, вы можете зарегистрировать PaymentService более статически:

container.RegisterType<PaymentService>(new InjectionConstructor(container.ResolveAll<IAccount>()));

Приведенный выше код зарегистрирует PaymentService и будет использовать коллекцию экземпляров IAccount, которая будет разрешена во время регистрации.

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

class PaymentService {

  IEnumerable<IAccount> accounts;

  public PaymentService (IUnityContainer container) {
    this.accounts = container.ResolveAll<IAccount>();
  }

  public void ProcessPayment() {
    foreach(var account in accounts) {
      account.Chackbalance();
    }
  }
}
//Registration is pretty clean in this case
container.RegisterType<IAccount, CreditCardAccount>();
container.RegisterType<PaymentService>();
container.RegisterInstance<IUnityContainer>(container);

Ответ 5

Если ваша озабоченность связана с Unity во всем приложении, вы можете объединить локатор службы с фасадом, чтобы скрыть реализацию IOC. Таким образом, вы не создаете зависимость от Unity в своем приложении, а только за то, что вы можете разрешать типы для вас.

Например:

public interface IContainer
{
    void Register<TAbstraction,TImplementation>();
    void RegisterThis<T>(T instance);
    T Get<T>();
}

public static class Container
{
    static readonly IContainer container;

    public static InitializeWith(IContainer containerImplementation)
    {
        container = containerImplementation;
    }

    public static void Register<TAbstraction, TImplementation>()
    {
        container.Register<TAbstraction, TImplementation>();
    }

    public static void RegisterThis<T>(T instance)
    {
        container.RegisterThis<T>(instance);
    }

    public static T Get<T>()
    {
        return container.Get<T>();
    }
}

Теперь вам нужна реализация IContainer для вашего контейнера IOC.

public class UnityContainerImplementation : IContainer
{
    IUnityContainer container;

    public UnityContainerImplementation(IUnityContainer container)
    {
        this.container = container;
    }

    public void Register<TAbstraction, TImplementation>()
    {
        container.Register<TAbstraction, TImplementation>();
    }

    public void RegisterThis<T>(T instance)
    {
        container.RegisterInstance<T>(instance);
    }

    public T Get<T>()
    {
        return container.Resolve<T>();
    }
}

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

Чтобы настроить локатор обслуживания:

IUnityContainer unityContainer = new UnityContainer();
UnityContainerImplementation containerImpl = new UnityContainerImplementation(unityContainer);
Container.InitializeWith(containerImpl);

Для тестирования вы можете создать заглушку IContainer, которая возвращает все, что вы хотите, и инициализировать Container с этим.