Ama*_*l K 7 .net c# oop fallback design-patterns
我有一个ScoreStrategy描述如何计算测验分数的课程:
public class ScoreStrategy
{
public int Id { get; set; }
public int QuizId { get; set; }
[Required]
public Quiz Quiz { get; set; }
public decimal Correct { get; set; }
public decimal Incorrect { get; set; }
public decimal Unattempted { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
三个属性Correct,Incorrect并Unattempted描述要为响应分配多少点。这些点也可以是负的。评分策略适用于测验中的所有问题,因此ScoreStrategy每个测验只能有一个。我有两个子类:
public class DifficultyScoreStrategy : ScoreStrategy
{
public QuestionDifficulty Difficulty { get; set; }
}
public class QuestionScoreStrategy : ScoreStrategy
{
[Required]
public Question Question { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
我的问题有三个难度级别(Easy, Medium, Hard;QuestionDifficulty是一个枚举)。该DifficultyScoreStrategy指定如果的具体困难需要问题点进行不同的分配。这将覆盖ScoreStrategy适用于整个测验的基础。每个难度级别可以有一个实例。
第三,我有一个QuestionScoreStrategy课程,指定是否必须以不同的方式授予特定问题的分数。这将覆盖测验范围ScoreStrategy和难度范围DifficultyStrategy。每个问题可以有一个实例。
在评估测验的反应时,我想实现一个逐级回退机制:
对于每个问题:
QuestionScoreStrategy,如果找到则返回策略。DifficultyScoreStrategy并检查是否存在针对所评估问题的难度级别的策略,如果找到策略则返回该策略。ScoreStrategy并检查是否存在,如果存在则返回它,ScoreStrategy两者都没有,请使用默认值{ Correct = 1, Incorrect = 0, Unattempted = 0 }(如果我也可以将其设置为可配置,那就太棒了,就像 .NET 的优雅方式:options => {
options.UseFallbackStrategy(
correct: 1,
incorrect: 0,
unattempted: 0
);
}
Run Code Online (Sandbox Code Playgroud)
)。
我已将上述信息汇总在一个表格中:
| 策略类型 | 优先事项 | 每个测验的最大实例数 |
|---|---|---|
QuestionScoreStrategy |
第一名(最高) | 测验中有多少问题 |
DifficultyScoreStrategy |
第二 | 4、每个难度级别一个 |
ScoreStrategy |
第三名 | 只有一个 |
| 回退策略 (默认 { Correct = 1, Incorrect = 0, Unattempted = 0}) |
第四名(最低) | 为整个应用程序配置。由所有测验共享 |
我有一个名为的容器类EvaluationStrategy,其中包含这些评分策略以及其他评估信息:
partial class EvaluationStrategy
{
public int Id { get; set; }
public int QuizId { get; set; }
public decimal MaxScore { get; set; }
public decimal PassingScore { get; get; }
public IEnumerable<ScoreStrategy> ScoreStrategies { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
我添加了一个调用上面GetStrategyByQuestion()相同EvaluationStrategy类的方法(注意它被声明为partial),它实现了这个回退行为,还有一个伴随索引器,它反过来调用这个方法。我已经声明了两个HashSet的种SDifficultyScoreStrategy和QuestionScoreStrategy和Initialize()方法实例化它们。然后所有的分数策略按类型切换并添加到适当的HashSet,ScoreStrategy每个测验只能有一个,它将存储在defaultStrategy:
partial class EvaluationStrategy
{
private ScoreStrategy FallbackStrategy = new() { Correct = 1, Incorrect = 0, Unattempted = 0 };
private ScoreStrategy defaultStrategy;
HashSet<DifficultyScoreStrategy> dStrategies;
HashSet<QuestionScoreStrategy> qStrategies;
public void Initialize()
{
qStrategies = new();
dStrategies = new();
// Group strategies by type
foreach (var strategy in strategies)
{
switch (strategy)
{
case QuestionScoreStrategy qs: qStrategies.Add(qs); break;
case DifficultyScoreStrategy ds: dStrategies.Add(ds); break;
case ScoreStrategy s: defaultStrategy = s; break;
}
}
}
public ScoreStrategy this[Question question] => GetStrategyByQuestion(question);
public ScoreStrategy GetStrategyByQuestion(Question question)
{
if (qStrategies is null || dStrategies is null)
{
Initialize();
}
// Check if question strategy exists
if (qStrategies.FirstOrDefault(str => str.Question.Id == question.Id) is not null and var qs)
{
return qs;
}
// Check if difficulty strategy exists
if (dStrategies.FirstOrDefault(str => str.Question.Difficulty == question.Difficulty) is not null and var ds)
{
return ds;
}
// Check if default strategy exists
if (defaultStrategy is not null)
{
return defaultStrategy;
}
// Fallback
return FallbackStrategy;
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法看起来有点笨拙,对我来说不太合适。使用部分类并添加到EvalutationStrategy似乎也不正确。如何实现这种逐级回退行为?有没有一种设计模式/原则,我可以在这里使用?如果未配置,我知道 .NET 框架中的许多内容会回退到默认约定。我需要类似的东西。或者有人可以简单地推荐一个具有更好可维护性的更简洁优雅的解决方案?
注意/附加信息:所有测验的ScoreStrategys 和EvaluationStrategy所有测验都存储在由 EF Core(.NET 5) 管理的数据库中,并带有 TPH 映射:
modelBuilder.Entity<ScoreStrategy>()
.ToTable("ScoreStrategy")
.HasDiscriminator<int>("StrategyType")
.HasValue<ScoreStrategy>(0)
.HasValue<DifficultyScoreStrategy>(1)
.HasValue<QuestionScoreStrategy>(2)
;
modelBuilder.Entity<EvaluationStrategy>().ToTable("EvaluationStrategy");
Run Code Online (Sandbox Code Playgroud)
我有一个基地DbSet<ScoreStrategy> ScoreStrategies和另一个DbSet<EvaluationStrategy> EvaluationStrategies. 因为EvaluationStrategy是一个 EF Core 类,所以我对GetStrategyByQuestion()向它添加 logic( )有点怀疑。
有一个名为Polly 的第三方库,它定义了名为Fallback的策略。
通过这种方法,您可以轻松定义如下后备链:
public ScoreStrategy GetStrategyByQuestionWithPolly(Question question)
{
Func<ScoreStrategy, bool> notFound = strategy => strategy is null;
var lastFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(FallbackStrategy);
var defaultFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(defaultStrategy);
var difficultyFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(() => GetApplicableDifficultyScoreStrategy(question));
var fallbackChain = Policy.Wrap(lastFallback, defaultFallback, difficultyFallback);
fallbackChain.Execute(() => GetApplicableQuestionScoreStrategy(question));
}
Run Code Online (Sandbox Code Playgroud)
QuestionScoreStrategy我提取了如下的策略选择逻辑DifficultyScoreStrategy:
private ScoreStrategy GetApplicableQuestionScoreStrategy(Question question)
=> qStrategies.FirstOrDefault(str => str.Question.Id == question.Id);
private ScoreStrategy GetApplicableDifficultyScoreStrategy(Question question)
=> dStrategies.FirstOrDefault(str => str.Difficulty == question.Difficulty);
Run Code Online (Sandbox Code Playgroud)
return声明如果您不想仅使用第三方库来定义和使用后备链,您可以执行以下操作:
public ScoreStrategy GetStrategyBasedOnQuestion(Question question)
{
var fallbackChain = new List<Func<ScoreStrategy>>
{
() => GetApplicableQuestionScoreStrategy(question),
() => GetApplicableDifficultyScoreStrategy(question),
() => defaultStrategy,
() => FallbackStrategy
};
ScoreStrategy selectedStrategy = null;
foreach (var strategySelector in fallbackChain)
{
selectedStrategy = strategySelector();
if (selectedStrategy is not null)
break;
}
return selectedStrategy;
}
Run Code Online (Sandbox Code Playgroud)
return声明