【问题标题】:C# - Pattern for iterating over predicatesC# - 用于迭代谓词的模式
【发布时间】:2016-12-23 10:39:24
【问题描述】:

我做了一个我不太喜欢的图案。

如下:

List<Element> listOfPossibleResults = getAllPossibleResults();

Element result = findResult(getFirstPriorityElements(listOfPossibleResults));
if (result!= null)
{
 return result;
}

result = findResult(getSecondPriorityElements(listOfPossibleResults));
if (result!= null)
{
 return result;
}

private Element findResult(List<Element> elements) {...};
private List<Element> getFirstPriorityElements(List<Element> elements) {...};
private List<Element> getSecondPriorityElements(List<Element> elements) {...};

等等。

基本上,我正在根据一些规则创建子列表。创建子列表后,我尝试在其中找到特定元素。如果我没有找到,我会转到下一个优先级,依此类推。

我想要一个可以迭代这些标准的解决方案,直到找到解决方案。但我不知道如何将它们变成我可以迭代的格式。

你们能给我这个问题的 C# 特定解决方案吗?

【问题讨论】:

  • 我认为您过度简化了代码。至少写出每个变量的类型和方法的return type
  • 为清楚起见编辑了问题。
  • 好的。第一种方法是将getFirstPriorityElementsgetSecondPriorityElements 合并,并使用getPriorityElements 之类的方法,该方法采用int priority 之类的附加参数....因此您可以创建一个循环并使用索引进行迭代。

标签: c# linq design-patterns iteration


【解决方案1】:

正如@Lepijohnny 提到的,您可以使用Chain of responsibility 设计模式。例如:

abstract class Handler<TRequest, TResult>
{
  protected Handler<TRequest, TResult> successor;

  public void SetSuccessor(Handler<TRequest, TResult> successor)
  {
    this.successor = successor;
  }

  public abstract TResult HandleRequest(TRequest request);
}

class FirstHandler : Handler<List<Element>, Element>
{
  public override void HandleRequest(TRequest request)
  {
    Element result = findResult(getFirstPriorityElements(request));
    if (result == null)
    {
      result = sucessor?.HandleRequest(request);
    }
    return result;
  }

  private Element findResult(List<Element> elements) {...};
  private List<Element> getFirstPriorityElements(List<Element> elements) {...};
}

class SecondHandler : Handler<List<Element>, Element>
{
  public override void HandleRequest(TRequest request)
  {
    Element result = findResult(getSecondPriorityElements(request));
    if (result == null)
    {
      result = sucessor?.HandleRequest(request);
    }
    return result;
  }

  private Element findResult(List<Element> elements) {...};
  private List<Element> getSecondPriorityElements(List<Element> elements) {...};
}

用法:

void Example()
{
  // Setup Chain of Responsibility
  var h1 = new FirstHandler();
  var h2 = new SecondHandler();
  h1.SetSuccessor(h2);

  var result = h1.Handle(new List<Element>());
}

这是一个简单的例子。我认为它描述了这种模式的工作原理,您可以根据需要对其进行调整。

【讨论】:

  • 太棒了!这就是我一直在寻找的。感谢您的宝贵时间!
  • 不,我还没有尝试过,但我现在明白如何实施解决方案了! :)
  • 这是一个有用的模式,但对于这种情况,它比必要的要重得多,如果我正确理解了这个问题,可以抽象出更多 - LINQ 仅适用于以下情况我们可以使用高阶函数来使代码更具声明性。
  • @Oly 我的想法正是我创建答案时的想法!
【解决方案2】:

在“结果”类中放置一个名为“Priority (int)”的属性,然后:

result = listOfPossibleResults.GroupBy(x => x.Priority).OrderBy(x => x.Key);

然后:

return result.FirstOrDefault(x => x.Count() > 0);

第一次检索时需要填写结果项的优先级。

附:我在这里输入了代码,如果某处有拼写错误,请见谅。

【讨论】:

  • 非常感谢您的回答。问题有点复杂。我没有写下子集创建的确切格式的原因是因为它比仅按优先级对元素进行分组更复杂。我的主要问题是如何迭代多个子句或谓词,并仅在找到元素时返回结果,而不是检查空值并手动将它们写下来。
【解决方案3】:

如果您可以将方法 getFirstPriorityElements(List list) 重构为单个 getPriorityElements(List list, int nr) 您可以执行以下操作

method IteratePredicates(List<> list, int nr = 0) 
{
    if (nr>maxpriority) return null;
    return findresult(getPriorityElements(list,nr)) ?? IteratePredicates(list,nr++);
}

在for循环中:

    method IteratePredicates(List<> list, int nr = 0)
    {
        for (int i = 0; i < maxpriority; i++)
        {
            var result = findresult(getPriorityElements(list, nr));
            if (result != null)
                return result;
        }
        return null;
    }

【讨论】:

  • 是的,可以,非常感谢。我仍在等待答案,因为从可读性的角度来看,我真的不喜欢递归,并且尽可能避免使用它们。如果我没有得到更适合我需要的答案,我会标记你。
  • 添加了非递归示例
【解决方案4】:

您的 get__PriorityElements 实际上是一个过滤器,我说得对吗?在这种情况下,这样处理它们更具声明性并且希望更具可读性:

Func<Element, bool> isFirstPriority = ...;
var firstPriorityElements = elements.Where(isFirstPriority);

现在您的总体目标是使用包含在findResult? 中的谓词从可能具有最高优先级的子序列中提取单个元素(或不提取)?所以用一个实际的谓词替换它

Func<Element, bool> isResult = ...;

像这样。现在您要查看所有第一优先级元素的isResult 匹配,如果没有找到所有第二优先级元素,等等。这听起来就像一个序列连接!所以我们最终得到了

var prioritisedSequence = elements
    .Where(isFirstPriority)
    .Concat(elements
        .Where(isSecondPriority))
    .Concat....;

最后是结果

var result = prioritisedSequence
    .FirstOrDefault(isResult);

由于WhereConcat 是惰性枚举的,因此它的好处是它是声明性的,同时避免了不必要的工作,而且它是轻量级的并且也是“LINQy”。

如果您想进一步抽象它,并预计优先级的排列方式会发生变化,您实际上可以为这样的人制作一个更高顺序的列表:

IEnumerable<Func<Element, bool>> priorityFilters = new[]
{
    isFirstPriority,
    isSecondPriority,
    ...
};

然后可以将连接作为对该序列的聚合来执行:

var prioritisedSequence = priorityFilters
    .Aggregate(
        Enumerable.Empty<Element>(),
        (current, filter) => current.Concat(elements.Where(filter)));

此更改可能会更容易在将来添加新的优先级,或者您可能会认为它会混乱并隐藏代码的意图。

【讨论】:

    【解决方案5】:

    您可以使用Func&lt;T, T&gt; 将方法视为对象,然后您也可以将它们放入例如数组。然后就可以遍历数组,一个个调用方法,直到找到结果。

    那么解决方案就变成了:

    var methods = new Func<List<Element>, List<Element>>[]
        { getFirstPriorityElements, getSecondPriorityElements };
    
    return methods
        .Select(method => findResult(method(listOfPossibleResults)))
        .Where(result => result != null)
        .FirstOrDefault();
    

    这简短易读,无需更改您的方法或类型即可工作,并且无需仅为应用模式而添加类。

    【讨论】:

      【解决方案6】:

      您可以使用规范模式
      这是一个示例代码:
      使用标准创建接口:

      public interface ISpecification<T>
      {
          Expression<Func<T, bool>> Criteria { get; }
      }
      

      然后创建一个包含查询规范的类:

      public class GlobalSongSpecification : ISpecification<Song>
      {
          public List<int> GenreIdsToInclude { get; set; } = new List<int>();
          public List<int> AlbumIdsToInclude { get; set; } = new List<int>();
          public List<string> ArtistsToInclude { get; set; } = new List<string>();
          public string TitleFilter { get; set; }
          public int MinRating { get; set; }
      
          [JsonIgnore]
          public Expression<Func<Song, bool>> Criteria
          {
              get
              {
                  return s =>
                      (!GenreIdsToInclude.Any() || s.Genres.Any(g => GenreIdsToInclude.Any(gId => gId == g.Id))) &&
                      (!AlbumIdsToInclude.Any() || AlbumIdsToInclude.Contains(s.AlbumId)) &&
                      (!ArtistsToInclude.Any() ||ArtistsToInclude.Contains(s.Artist)) &&
                      (String.IsNullOrEmpty(this.TitleFilter) || s.Title.Contains(TitleFilter)) &&
                      s.Rating >= MinRating;
              }
          }
      }
      

      使用公开接收 ISpecification 的方法创建存储库:

       public interface ISongRepository
      {
          IEnumerable<Song> List(ISpecification<Song> specification);
          //IQueryable<Song> List();
          Song GetById(int id);
          void Add(Song song);
          IEnumerable<string> AllArtists();
          IEnumerable<Genre> AllGenres();
      }
      

      您的客户端代码调用 GlobalSongSpecification,填充它并将其传递到存储库,以便按条件过滤:

      public ActionResult Index(List<int> selectedGenres = null, 
              List<string> selectedArtists = null, 
              string titleSearch = null,
              int minRating = 0,
              string filter = null,
              string save = null,
              string playlistName = null)
          {
              if (selectedArtists == null) { selectedArtists = new List<string>(); }
              if (selectedGenres == null) { selectedGenres = new List<int>(); }
      
              var spec = new GlobalSongSpecification();
              spec.ArtistsToInclude.AddRange(selectedArtists);
              spec.GenreIdsToInclude.AddRange(selectedGenres);
              spec.MinRating = minRating;
              spec.TitleFilter = titleSearch;
      
              var songs = _songRepository.List(spec);
      
              //You can work with the filtered data at this point
          }
      

      然后您填充剃刀视图或将其公开为 Web api。 示例代码来自复数设计模式库课程Here(Specification Pattern module)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-24
        • 1970-01-01
        • 2018-09-13
        • 1970-01-01
        • 2014-07-26
        • 1970-01-01
        • 1970-01-01
        • 2012-12-23
        相关资源
        最近更新 更多