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

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

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

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

Michel

РЕДАКТИРОВАТЬ: обнаружил, что глобальная статическая служба называется локатор службы, добавил это к заголовку.

Ответы [ 5 ]

11 голосов
/ 09 июня 2010

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

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

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

7 голосов
/ 25 июня 2010

+ 1 для зная , что сервисный локатор плохая вещь .

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

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

5 голосов
/ 09 июня 2010

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

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

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

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

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

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

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

4 голосов
/ 10 июня 2010

Теоретически, чтобы не беспокоиться о наличии статического экземпляра IoC, вы должны следовать правилу Fight Club - т.е. не говорить о бойцовском клубе - то есть не упоминать контейнер 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 выполнить разрешение учетных записей. Это не совсем соответствует правилу Бойцовского клуба, но немного менее вонючий, чем статический Service Locator.

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);
2 голосов
/ 10 июня 2010

Если ваша задача зависеть от 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 этим.

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