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

Для объектов неизменяемых значений, таких как int, строки или даты, идентификация объекта не особенно полезна. Лучше думать о равенстве. Идентификация - это, по сути, деталь реализации объектов значения - поскольку они неизменяемы, нет эффективной разницы между наличием нескольких ссылок на один и тот же объект или несколько объектов.

30
задан Michel 20 June 2010 в 12:19
поделиться

5 ответов

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

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

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, потому что container.Resolve создаст все свои зависимости - и зависимости своих зависимостей, вплоть до конечных классов без каких-либо собственных зависимостей.


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

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

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

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

11
ответ дан 28 November 2019 в 00:21
поделиться

Если ваша проблема связана с зависимостью от 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 , которая возвращает все, что вы хотите, и инициализировать этим контейнер .

2
ответ дан 28 November 2019 в 00:21
поделиться

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

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

Таким образом, при начальной загрузке приложения:

  1. Создайте экземпляр вашего контейнера
  2. Зарегистрируйте все свои полезности
  3. Разрешите основные классы (это приведет к включению всех других необходимых вам зависимостей)
  4. Запустите команду " main »часть приложения

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

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

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

5
ответ дан 28 November 2019 в 00:21
поделиться

Теоретически, чтобы не беспокоиться о наличии статического экземпляра 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>();

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

Для нашего примера оплаты предположим, что вы хотите перечислить все учетные записи и проверить их балансы:

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);
4
ответ дан 28 November 2019 в 00:21
поделиться

+1 за , зная , что локатор услуг плохой .

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

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

7
ответ дан 28 November 2019 в 00:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: