[英]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);
由於Where
和Concat
被懶惰地枚舉,因此它具有聲明性,同時避免了超出必要的工作,並且它也是輕量級和“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.