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

Избежать локатора обслуживания Antipattern с устаревшим приложением, не предназначенным для IOC

Я часто читал, что Сервис-локаторы в IOC являются анти-шаблонами.

В прошлом году мы внедрили IOC (Ninject специально) в наше приложение на работе. Приложение является наследием, оно очень большое и фрагментировано. Существует множество способов создания класса или цепочки классов. Некоторые из них созданы веб-каркасом (который является обычным), некоторые из них создаются nHibernate. Лоты просто разбросаны по странным местам.

Как мы будем обрабатывать разные сценарии и не придумывать что-то, что не является, по крайней мере, ServiceLocatorish и не заканчиваться разными ядрами в разных местах (важны такие области, как singleton, HttpRequest и thread).

Изменить. Я добавлю немного более подробную информацию о том, что привело нас к нашему текущему шаблону SL.

На самом деле нам не нужны несколько ядер. Мы просто хотим одного (и действительно, из-за SL мы имеем только один статический). Вот наша настройка:

1) У нас есть модули Ninject в 7-8 разных проектах/сборках. Когда наше приложение (webapp) запускает модули, они собираются с помощью сканирования сборок и загружаются в ядро ​​и помещаются в Service Locator. Так что все это довольно дорого.

2) У нас есть пользовательский интерфейс, который строится счастливым. Подумайте о 120 табличных формах, каждая из которых построит 1-10 вкладки в качестве части их жизненного цикла. SL стратегически используется в 5-6 местах, которые охватывают все это либо как чистое разрешение, либо вводятся только после введения свойств свойств.

3) Все, что связано с пользовательским интерфейсом, не покрывается этими вызовами верхнего уровня, и если эти классы хотят использовать МОК, им необходимо разработать свою собственную стратегию. Существуют разные разные рамки, каждая из которых имеет свои собственные маленькие миры.

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

Но откуда я получаю это ядро? Я не хочу создавать и регистрировать новую повсюду. Похоже, мне пришлось бы вытащить его из статического контекста или factory, чтобы я мог гарантировать, что ядро, которое я использую, держится за те же объекты с областью, что все остальные используют, а также во избежание расходов на регистрацию всех модулей. В этот момент, что бы это ни было, похоже, похоже на "Локатор сервисов"?

Имейте в виду, что это приложение ОГРОМНОЕ и тесно связанное. У нас нет роскоши потратить несколько месяцев на то, чтобы реорганизовать это. SL казался хорошим способом для нас медленно работать МОК, где мы могли, как мы успели.

4b9b3361

Ответ 1

Итак, идеальный способ сделать это из того, что Я прочитал, вводя ядро когда вам нужно получить доступ к МОК... хорошо что все хорошо и хорошо; мы сохраняем использование SL до минимума.

Нет, инъекция ядра в ваши бизнес-классы - не лучший способ. Лучший способ - создать factory, например. IFooFactory { IFoo Create(); } или Func<IFoo>, и пусть это создаст новый экземпляр.

Реализация этого интерфейса переходит в составной корень, получает экземпляр ядра и разрешает использование ядра. Это позволяет вашим классам освобождаться от конкретного контейнера, и вы можете повторно использовать их в другом проекте с использованием другого контейнера. В случае Func вы можете использовать следующий модуль: Поддерживает ли Ninject Func (автоматически сгенерированный factory)? Ninject 2.4 будет иметь встроенную поддержку для этого.


Что касается рефакторинга, то вряд ли можно сказать вам, как лучше всего идти, не зная исходного кода вашего приложения. Я могу просто дать вам одобрение, которое, вероятно, может работать.

Я предполагаю, что вы хотите реорганизовать все приложение на соответствующий DI в долгосрочной перспективе. То, что я сделал однажды для довольно крупного проекта (30-40 человеко-лет), состояло в следующем:

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

Пример псевдокода:

Foo{ ServiceLocator.Get<Service1>(), new Bar() }
Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<Service3>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass()}

Шаг 1 - Изменить корень (Foo)

Foo{ ctor(IService1, IBar) }
Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<IService2>() }
Service2 { ServiceLocator.Get<IService3>() }
Service3 { new SomeClass()}

Bind<IBar>().To<Bar>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());

Шаг 2 - Изменение зависимостей root

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass()}

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());

Шаг 3 - измените их зависимости и продолжайте, пока вы не окажетесь на листах

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass() }

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
Bind<IService3>().ToMethod(ctx => ServiceLocator.Get<IService3>());

Шаг 4 - Рефакторинг служб, которые не зависят от других

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { ctor(ISomeClass) }

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
Bind<IService3>().To<Service3>().InSingletonScope();

Шаг 5 - Следующий рефакторинг тех, которые зависят от служб, которые только реорганизовали службы как зависимость.

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ctor(IService3) }
Service3 { ctor(ISomeClass) }

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().To<Service2>().InSingletonScope();
Bind<IService3>().To<Service3>().InSingletonScope();

Шаг 6 - Повторяйте до тех пор, пока не будут реорганизованы все службы.

Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ctor(IService2) }
Service2 { ctor(IService3) }
Service3 { ctor(ISomeClass) }

Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().To<Service1>().InSingletonScope();
Bind<IService2>().To<Service2>().InSingletonScope();
Bind<IService3>().To<Service3>().InSingletonScope();

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