Как реализовать использование нескольких стратегий во время выполнения

#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));
     }
  }  
}