Как избежать антивирусной программы Locator?
Я пытаюсь удалить Service Locator из абстрактного базового класса, но я не уверен, что заменить его. Вот псевдо-пример того, что у меня есть:
public abstract class MyController : Controller
{
protected IKernel kernel;
public MyController(IKernel kernel) { this.kernel = kernel); }
protected void DoActions(Type[] types)
{
MySpecialResolver resolver = new MySpecialResolver(kernel);
foreach(var type in types)
{
IMyServiceInterface instance = resolver.Get(type);
instance.DoAction();
}
}
}
Проблема заключается в том, что instanciator производного класса не знает, какие привязки должны иметь ядро, чтобы сохранить MySpecialResolver
от выброса исключения.
Это может быть неотъемлемо неразрешимым, потому что я не знаю, из каких типов мне придется решать. Производные классы отвечают за создание параметра types
, но они нигде не закодированы. (Типы основаны на наличии атрибутов в пределах иерархии композиций производного класса.)
Я пытаюсь исправить это с ленивыми делегатами по загрузке, но до сих пор я не придумал чистого решения.
Update
Здесь есть две проблемы: одна - контейнер IoC передается контроллеру, действуя как локатор сервисов. Это легко удалить - вы можете перемещать местоположение вверх или вниз по стеку вызовов, используя всевозможные методы.
Вторая проблема сложная: как вы можете обеспечить, чтобы контроллер имел необходимые сервисы, когда требования не отображаются до выполнения. С самого начала это должно было быть очевидным: вы не можете! Вы всегда будете зависеть от состояния локатора службы или содержимого коллекции. В этом конкретном случае никакая проблема не позволит решить проблему, описанную в в этой статье со статически типизированными зависимостями. Я думаю, что то, что я собираюсь сделать, это передать Lazy-массив в конструктор контроллера и выбросить исключение, если отсутствует требуемая зависимость.
Ответы
Ответ 1
Возможно, вам нужно просто удалить ядро, типы и MySpecialResolver и позволить подклассам вызвать DoActions с экземплярами IMyServiceInterface, которые им нужны в качестве аргумента напрямую. И пусть подклассы решают, как они попадают в эти экземпляры - они должны знать лучше (или в случае, если они не знают, кто именно тот, кто когда-либо решает, какие экземпляры IMyServiceInterface нужны)
Ответ 2
Я согласен с @chrisichris и @Mark Seemann.
Отключите ядро от контроллера. Я бы немного изменил состав вашего резольвера, чтобы ваш контроллер мог удалить зависимость от контейнера IoC и разрешить распознавателю быть единственным элементом, который беспокоится о контейнере IoC.
Тогда я бы разрешил преобразовать передатчик в конструктор контроллера. Это позволит вашему контроллеру быть гораздо более проверяемым.
Например:
public interface IMyServiceResolver
{
List<IMyServiceInterface> Resolve(Type[] types);
}
public class NinjectMyServiceResolver : IMyServiceResolver
{
private IKernal container = null;
public NinjectMyServiceResolver(IKernal container)
{
this.container = container;
}
public List<IMyServiceInterface> Resolve(Type[] types)
{
List<IMyServiceInterface> services = new List<IMyServiceInterface>();
foreach(var type in types)
{
IMyServiceInterface instance = container.Get(type);
services.Add(instance);
}
return services;
}
}
public abstract class MyController : Controller
{
private IMyServiceResolver resolver = null;
public MyController(IMyServiceResolver resolver)
{
this.resolver = resolver;
}
protected void DoActions(Type[] types)
{
var services = resolver.Resolve(types);
foreach(var service in services)
{
service.DoAction();
}
}
}
Теперь ваш контроллер не связан с конкретным контейнером IoC. Кроме того, ваш контроллер гораздо более проверен, так как вы можете издеваться над преобразователями и вообще не использовать контейнер IoC для своих тестов.
В качестве альтернативы, если вам не удастся управлять при создании экземпляра контроллера, вы можете немного изменить его:
public abstract class MyController : Controller
{
private static IMyServiceResolver resolver = null;
public static InitializeResolver(IMyServiceResolver resolver)
{
MyController.resolver = resolver;
}
public MyController()
{
// Now we support a default constructor
// since maybe someone else is instantiating this type
// that we don't control.
}
protected void DoActions(Type[] types)
{
var services = resolver.Resolve(types);
foreach(var service in services)
{
service.DoAction();
}
}
}
Затем вы вызываете это при запуске вашего приложения для инициализации преобразователя:
MyController.InitializeResolver(new NinjectMyServiceResolver(kernal));
Мы сделали это для обработки элементов, созданных в XAML, которые нуждаются в зависимостях, но мы хотели удалить запросы службы Locator как запросы.
Прошу прощения за любые синтаксические ошибки:)
Я пишу серию сообщений в блоге по теме рефакторинга приложения MVVM с вызовами Locator Service в моделях просмотра, которые могут показаться вам интересными. Часть 2 скоро появится:)
http://kellabyte.com/2011/07/24/refactoring-to-improve-maintainability-and-blendability-using-ioc-part-1-view-models/
Ответ 3
Мне хотелось бы получить немного больше информации, прежде чем публиковать этот ответ, но Келли поставила меня на место.:) Расскажи мне, чтобы я поместил свой код, где мой рот, так сказать.
Как я уже сказал в своем комментарии к Келли, я не согласен с тем, чтобы переместить распознаватель/локатор из статической реализации в внедренную реализацию. Я согласен с ChrisChris в том, что зависимости, необходимые для производного типа, должны быть разрешены в этом классе и не делегированы базовому классу.
Итак, вот как я удалю местоположение службы...
Создать командный интерфейс
Прежде всего, я бы создал командный интерфейс для конкретной реализации. В этом случае типы, отправленные с помощью метода DoActions, генерируются из атрибутов, поэтому я бы создал IAttributeCommand
. Я добавляю метод Matches
к команде, чтобы объявить команду для использования определенными типами.
public interface IAttributeCommand
{
bool Matches(Type type);
void Execute();
}
Добавить реализации команд
Чтобы реализовать интерфейс, я передаю конкретные зависимости, которые мне нужны для выполнения моей команды (которая будет разрешена моим контейнером). Я добавляю предикат к методу "Матчи" и определяю свое поведение Execute.
public class MyTypeAttributeCommand : IAttributeCommand
{
MyDependency dependency;
SomeOtherDependency otherDependency;
public MyTypeAttributeCommand (MyDependency dependency, ISomeOtherDependency otherDependency)
{
this.dependency = dependency;
this.otherDependency = otherDependency
}
public bool Matches(Type type)
{
return type==typeof(MyType)
}
public void Execute()
{
// do action using dependency/dependencies
}
}
Команды регистрации с контейнером
В StructureMap (используйте ваш любимый контейнер), я бы зарегистрировал массив следующим образом:
Scan(s=>
{
s.AssembliesFromApplicationBaseDirectory();
s.AddAllTypesOf<IAttributeCommand>();
s.WithDefaultConventions();
}
Команды выбора и выполнения на основе типа
Наконец, в базовом классе я определяю массив IAttributeCommand
в моих аргументах конструктора, которые должны быть введены контейнером IOC. Когда производный тип передается в массиве types
, я буду выполнять правильную команду на основе предиката.
public abstract class MyController : Controller
{
protected IAttributeCommand[] commands;
public MyController(IAttributeCommand[] commands) { this.commands = commands); }
protected void DoActions(Type[] types)
{
foreach(var type in types)
{
var command = commands.FirstOrDefault(x=>x.Matches(type));
if (command==null) continue;
command.Execute();
}
}
}
Если несколько команд могут обрабатывать один тип, вы можете изменить реализацию: commands.Where(x=>x.Matches(type)).ToList().ForEach(Execute);
Эффект тот же, но есть тонкая разница в том, как создается класс. Класс не имеет связи с контейнером IOC, и нет места службы. Реализация более тестируема, поскольку класс может быть сконструирован с его реальными зависимостями, без необходимости подключать контейнер/резольвер.