How to implement usage of multiple strategies at runtime

可紊 提交于 2019-12-23 08:28:09

问题


I need to process a list of records returned from a service.
However the processing algorithm for a record changes completely based on a certain field on the record.
To implement this , I have defined an IProcessor interface which has just one method :

public interface IProcessor
{         
    ICollection<OutputEntity> Process(ICollection<InputEntity>> entities);
}

And I have two concrete implementations of IProcessor for the different types of processing.
The issue is that I need to use all implementations of IProcessor at the same time .. so how do I inject the IProcessor into my Engine class which drives the whole thing:

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

This is what I'm doing at present .. and it doesn't look nice and clean.
Any ideas as to how I could improve this design .. using IoC perhaps ?


回答1:


You could put the SomeField specification in the IProcessor implementations (you'd have to add an extra field to the IProcessor interface), and find the corresponding records based on the processor you're using at the moment.

A bit of code to clear that up:

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

Alternatively, you could provide the IProcessors in the ProcessRecords method, or set them as Properties in the Engine class ( though I prefer the constructor injection ).

Edit

You might also want to look into the CanProcess approach in the other answers. Though the principle is the same, it provides an even more extensible/robust solution if you need to change the criteria to decide if the processor should process the types.




回答2:


Change your IProcessor interface, and add a new function:

public interface IProcessor
{         
    ICollection<OutputEntity> Process(InputEntity> entity);
    bool CanProcess (InputEntity entity);
}

Then your code doesn't need to know anything about the implementation:

foreach (var entity in entities) {
    var processor = allOfMyProcessors.First(p=>p.CanProcess(entity));

    processor.Process(entity);
}

Your processor would do the magic:

public class Processor1 : IProcessor {
    public bool CanProcess(InputEntity entity) {
        return entity.Field == "field1";
    }
}

Benefit of this approach is that you can load new processors from assemblies etc. without your main code implementation having to know about any single implementation out there.




回答3:


Personally I would probably make one IProcessor implementation that handles the different record types. Something like

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 result;
    }
}

Then you can pass all you input entities to the Process method without worrying which record types it contains.

This implementation lacks some extendability, so if that's needed it has to be extended (see Komet's answer). The basic idea is to have one separate service responsible for choosing process implementation.




回答4:


This might look a little bit complicated, but you can use attributes to mark your processors, and at runtime read the attributes and see what processor you have and build a dictionary out of it:

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


来源:https://stackoverflow.com/questions/6029254/how-to-implement-usage-of-multiple-strategies-at-runtime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!