如何在运行时实现多个策略的使用

本文关键字:策略 运行时 实现 | 更新日期: 2023-09-27 17:56:45

我需要处理从服务返回的记录列表。
但是,记录的处理算法完全根据记录上的某个字段而更改。
为了实现这一点,我定义了一个只有一个方法的IProcessor接口:

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

我有两个具体的IProcessor实现,用于不同类型的处理。
问题是我需要同时使用IProcessor的所有实现..那么如何将IProcessor注入到我的引擎类中,从而驱动整个事情:

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 ?

如何在运行时实现多个策略的使用

更改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";
    }
}

这种方法的好处是,您可以从程序集等加载新的处理器,而无需主代码实现知道任何单个实现。

您可以将 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));
        }
    }
}

或者,您可以在 ProcessRecords 方法中提供 IProcessors,或者将它们设置为 Engine 类中的属性(尽管我更喜欢构造函数注入)。

编辑

您可能还想在其他答案中研究CanProcess方法。尽管原理相同,但如果您需要更改条件以决定处理器是否应处理类型,它提供了更具可扩展性/健壮性的解决方案。

就我个人而言,我可能会制作一个处理不同记录类型的 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 result;
    }
}

然后,您可以将所有输入实体传递给 Process 方法,而不必担心它包含哪些记录类型。

这个实现缺乏一些可扩展性,所以如果需要,它必须被扩展(参见Komet的答案)。基本思想是让一个单独的服务负责选择流程实现。

这可能看起来有点复杂,但您可以使用属性来标记处理器,并在运行时读取属性并查看您拥有的处理器并从中构建字典:

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