簡體   English   中英

C# - 用於迭代謂詞的模式

[英]C# - Pattern for iterating over predicates

我做了一個我不喜歡的模式。

它如下:

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#特定的問題解決方案嗎?

正如@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>());
}

這是一個很快的例子。 我認為它描述了這種模式是如何工作的,你可以根據自己的需要進行調整。

在“result”類中放入一個名為“Priority(int)”的屬性,然后:

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

然后:

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

首次檢索時,您需要填寫結果項的優先級。

PS我在這里鍵入代碼,如果某處有拼寫錯誤,請原諒我。

如果您可以將方法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;
    }

您可以使用Func<T, T>將方法視為對象,然后您也可以將它們放在例如數組中。 然后,您可以迭代數組,逐個調用方法,直到找到結果。

然后解決方案變為:

var methods = new Func<List<Element>, List<Element>>[]
    { getFirstPriorityElements, getSecondPriorityElements };

return methods
    .Select(method => findResult(method(listOfPossibleResults)))
    .Where(result => result != null)
    .FirstOrDefault();

這是簡短且可讀的,無需更改方法或類型即可工作,並且無需為了應用模式而添加類。

我是對的,你的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)));

此更改可能會使將來更容易添加新的優先級,或者您可能認為它會混淆並隱藏代碼的意圖。

您可以使用規格模式
這是一個示例代碼:
使用條件創建接口:

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公開。 示例代碼來自pluralsight desing模式庫課程Here(Specification Pattern module)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM