Отказ от службы локатора службы Antipattern с устаревшим приложением, не предназначенным для МОК - PullRequest
12 голосов
/ 03 июля 2011

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

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

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

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

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

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

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

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

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

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

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

1 Ответ

12 голосов
/ 05 июля 2011

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

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

Реализация этого интерфейса входит в составной корень, получает экземпляр ядра и выполняетрешение с использованием ядра.Это освобождает ваши классы от конкретного контейнера, и вы можете использовать их в другом проекте, используя другой контейнер.В случае Func вы можете использовать следующий модуль: Поддерживает ли Ninject Func (автоматически сгенерированная фабрика)? 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();

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

...