#c# #strategy-pattern
#c# #стратегия-шаблон
Вопрос:
Мне нужно обработать список записей, возвращенных из сервиса.
Однако алгоритм обработки записи полностью меняется в зависимости от определенного поля в записи.
Для реализации этого я определил интерфейс IProcessor, который имеет только один метод :
public interface IProcessor
{
ICollection<OutputEntity> Process(ICollection<InputEntity>> entities);
}
И у меня есть две конкретные реализации IProcessor
для разных типов обработки.
Проблема в том, что мне нужно использовать все реализации IProcessor
одновременно .. итак, как мне внедрить IProcessor
в мой класс Engine, который управляет всем этим:
public class Engine
{
public void ProcessRecords(IService service)
{
var records = service.GetRecords();
var type1Records = records.Where(x => x.SomeField== "Type1").ToList();
var type2Records = records.Where(x => x.SomeField== "Type2").ToList();
IProcessor processor1 = new Type1Processor();
processor.Process(type1Records);
IProcessor processor2 = new Type2Processor();
processor.Process(type2Records);
}
}
Это то, что я делаю в настоящее время .. и это не выглядит красиво и чисто.
Есть идеи относительно того, как я мог бы улучшить этот дизайн .. возможно, используя IoC?
Ответ №1:
Измените свой IProcessor
интерфейс и добавьте новую функцию:
public interface IProcessor
{
ICollection<OutputEntity> Process(InputEntity> entity);
bool CanProcess (InputEntity entity);
}
Тогда вашему коду не нужно ничего знать о реализации:
foreach (var entity in entities) {
var processor = allOfMyProcessors.First(p=>p.CanProcess(entity));
processor.Process(entity);
}
Ваш процессор сотворил бы волшебство:
public class Processor1 : IProcessor {
public bool CanProcess(InputEntity entity) {
return entity.Field == "field1";
}
}
Преимущество этого подхода заключается в том, что вы можете загружать новые процессоры из сборок и т.д. без необходимости вашей основной реализации кода знать о какой-либо отдельной реализации.
Комментарии:
1. Я бы тоже это сделал, но я бы взял все соответствующие процессоры вместо только первого (но это может не соответствовать требованиям OP)
2. Большое спасибо за ответы so may, ребята.. Я думаю, я мог бы использовать все это. Независимо от подхода, который я использую.. Как мне внедрить IEnumerable<Processor> в класс Engine?
Ответ №2:
Вы могли бы поместить спецификацию someField в реализации IProcessor (вам пришлось бы добавить дополнительное поле в интерфейс IProcessor) и найти соответствующие записи на основе процессора, который вы используете в данный момент.
Немного кода, чтобы прояснить это:
public interface IProcessor
{
ICollection<OutputEntity> Process(ICollection<InputEntity>> entities);
string SomeField{get;set;}
}
public class Engine
{
public Engine(IEnumerable<IProcessor> processors)
{
//asign the processors to local variable
}
public void ProcessRecords(IService service)
{
// getRecords code etc.
foreach(var processor in processors)
{
processor.Process(typeRecords.Where(typeRecord => typeRecord.SomeField == processor.SomeField));
}
}
}
В качестве альтернативы, вы могли бы предоставить IProcessors в методе processRecords или установить их в качестве свойств в классе Engine (хотя я предпочитаю внедрение конструктора).
Редактировать
Возможно, вы также захотите изучить подход CanProcess в других ответах. Хотя принцип тот же, он предоставляет еще более расширяемое / надежное решение, если вам нужно изменить критерии, чтобы решить, должен ли процессор обрабатывать типы.
Комментарии:
1. Спасибо KoMet, это намного лучше, чем моя существующая реализация. Хотя еще один вопрос .. как мне внедрить экземпляр IEnumerable<IProcessor> ? . Я не хочу вручную создавать эти процессоры. Я хочу использовать IoC (Unity / castle) для этого .. но тогда у меня было бы зарегистрировано несколько типов с одним и тем же интерфейсом .. это разрешено?
2. Да, это определенно типичное использование библиотек IoC. Вам нужно будет определить все ваши реализации IProcessor в некотором файле конфигурации, и IoC должен иметь возможность заполнять коллекции экземплярами ваших реализаций.
3. Вы можете внедрить их вручную, передав в конструктор (который уже является IoC), или вы можете использовать целый массив контейнеров IoC (Ninject, Unity, … ). У них есть особенности относительно того, как выполнять IoC, либо с помощью файла конфигурации, либо с помощью некоторой конфигурации в коде (например, в методе global.asax или main ), либо с помощью атрибутов (например, MEF ).
Ответ №3:
Лично я бы, вероятно, создал одну реализацию IProcessor, которая обрабатывает разные типы записей. Что-то вроде
public class ProcessorImpl : IProcessor
{
// Either create them here or get them from some constructor injection or whatever.
private readonly Type1Processor type1 = new Type1Processor();
private readonly Type2Processor type2 = new Type2Processor();
public ICollection<OutputEntity> Process(ICollection<InputEntity>> entities)
{
var type1Records = records.Where(x => x.SomeField== "Type1").ToList();
var type2Records = records.Where(x => x.SomeField== "Type2").ToList();
var result = new List<OutputEntity>();
result.AddRange(type1.Process(type1Records));
result.AddRange(type2.Process(type2Records));
return resu<
}
}
Затем вы можете передать все введенные вами объекты методу Process, не беспокоясь о том, какие типы записей он содержит.
Этой реализации не хватает некоторой расширяемости, поэтому, если это необходимо, ее необходимо расширить (см. Ответ Komet). Основная идея заключается в том, чтобы иметь одну отдельную службу, ответственную за выбор реализации процесса.
Ответ №4:
Это может показаться немного сложным, но вы можете использовать атрибуты для обозначения своих процессоров, а во время выполнения прочитать атрибуты и посмотреть, какой у вас процессор, и создать из него словарь:
public interface IProcessor
{
ICollection<OutputEntity> Process(ICollection<InputEntity>> entities);
}
[Processor("Type1")]
public class Processor1 : IProcessor
{
}
[Processor("Type2")]
public class Processor1 : IProcessor
{
}
public class Engine
{
Dictionary<string, IProcessor> processors;
public Engine()
{
// use reflection to check the types marked with ProcessorAttribute and that implement IProcessor
// put them in the processors dictionary
// RegisterService(type, processor);
}
public RegisterService(string type, IProcessor processor)
{
processor[type] = processor;
}
public void ProcessRecords(IService service)
{
var records = service.GetRecords();
foreach(var kvp in processors)
{
kvp.Value.Process(records.Where(record => record.SomeField == kvp.Key));
}
}
}