重要免责声明
在计算机科学中,总是存在着一种权衡考虑。
我建议的解决方法需要提高你的解决方案的工程级别。然而这并不总是正确的做法,因为它会增加代码的抽象级别。
如果这段代码不经常更改,可能的排序条件较少,并且你认为它将保持不变的话,你可以继续使用当前的实现(或基于switch语句的等效解决方案,这在代码可读性方面可能更好)。
为什么使用if串是个问题
除了代码可读性的问题外,长长的一串if
语句的主要问题是违反了开闭原则。您可以在这里找到有关该原则的解释。
简而言之,这意味着像您展示的代码这样的代码会不断发生变化,因为每次引入新条件(或删除现有条件)时,必须通过添加(或删除)一个新的if
语句来修改现有代码。这不是理想的情况,因为更改已经工作的现有代码可能会导致错误,并且在时间和注意力方面是一项昂贵的操作。理想的情况是拥有一段良好运行、经过充分测试并且可以保持稳定的代码。为了满足新的要求,我们希望编写新模块(例如,新类),而不触及任何现有的工作模块。
将if串更改为switch语句可行吗?
不可行。 将if串转换为switch
可能会提高代码的可读性,但它并不是违反开闭原则的根本解决方案。维护switch
语句时受到与级联if
语句相同的问题的影响。
设计模式来拯救。
首先,尝试应用以下设计模式,这基本上是策略模式。
请注意,使用此方法,通过编写接口的新实现,您可以扩展程序的功能,但您永远不必修改现有的接口实现和使用该接口的代码。
IProductsProvider
{
bool CanHandleCondition(string condition);
Task<IEnumerable<Products>> GetProducts(string condition);
}
class TitleAscendingProvider : IProductsProvider
{
private readonly IApiClient _apiClient;
public TitleAscendingProvider(IApiClient apiClient)
{
_apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
}
public bool CanHandleCondition(string condition) => condition == "TitleASC";
public async Task<IEnumerable<Products>> GetProducts(string condition)
{
var products = await _apiClient.GetProducts();
return products.OrderBy(c => c.Title);
}
}
class TitleDescendingProvider : IProductsProvider
{
private readonly IApiClient _apiClient;
public TitleAscendingProvider(IApiClient apiClient)
{
_apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
}
public bool CanHandleCondition(string condition) => condition == "TitleDESC";
public async Task<IEnumerable<Products>> GetProducts(string condition)
{
var products = await _apiClient.GetProducts();
return products.OrderByDescending(c => c.CategoriesTitle);
}
}
class CompositeProvider : IProductsProvider
{
private readonly IProductsProvider[] _providers;
public TitleAscendingProvider(IEnumerable<IProductsProvider> providers)
{
if (providers is null)
{
throw new ArgumentNullException(nameof(providers));
}
_providers = providers.ToArray();
}
public bool CanHandleCondition(string condition) => _providers.Any(p => p.CanHandleCondition(condition));
public Task<IEnumerable<Products>> GetProducts(string condition)
{
var provider = _providers.FirstOrDefault(p => p.CanHandleCondition(condition));
if (provider == null)
{
throw new InvalidOperationException("Unable to find a proper provider for the condition '{condition}'")
}
return provider.GetProducts(condition);
}
}
private readonly IProductsProvider _provider;
public async Task<ActionResult<IEnumerable<Product>>> FilterAsync(string conditionName)
{
var products = await _provider.GetProducts(conditionName);
return Ok(products);
}
基于表达式树的另一种可能方法
另一种可能的方法是使用表达式树。表达式树基本上是代表某些代码的对象,您可以在运行时检查它,以分析它并对其执行有用的操作。给定一个表达式树可以进行的其中一件事是编译它: 这样做会得到一个委托,您可以执行它。换句话说,您从代表某些代码的对象开始,最终得到一个委托,您可以调用以执行由表达式树表示的代码。
这种方法的基本思想是询问用户他想要使用哪个字段对产品进行排序以及排序方向(升序或降序)。然后,您可以构建一个表达式树来表示一些代码,该代码给定产品类的一个实例,访问必须用于对产品进行排序的属性。最后,您可以编译表达式树以获取一个委托实例,您可以将其传递给LINQ to object OrderBy
扩展方法。
这种解决方案并不完全适用于您的问题,因为它需要调用通用方法,并且无法从用户输入(用于对产品进行排序的属性名称)中推断出通用类型参数,但我认为将表达式树提到这里是值得一提的。把我的回答中的这一部分看作进一步研究表达式树世界的触发器。
以下代码是使用表达式树来获取用于排序某些对象的委托的示例,从要排序的属性名称开始。
static class Program
{
public static void Main(string[] args)
{
var people = new Person[]
{
new Person { Name = "Bob", Age = 11 },
new Person { Name = "Alice", Age = 8 },
new Person { Name = "Tony", Age = 1 }
};
var propertyName = nameof(Person.Age);
var expression = GetPropertyExpression<int>(propertyName);
var func = expression.Compile();
var sorted = people.OrderBy(func).ToArray();
}
private static Expression<Func<Person, T>> GetPropertyExpression<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(Person), "model");
var property = Expression.Property(parameter, propertyName);
var expression = Expression.Lambda<Func<Person, T>>(property, parameter);
return expression;
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
欢迎任何有更深入理解表达式树的社区成员来改进这个想法,或者解释为什么表达式树不适用于这个问题的场景。
在
此处,您可以找到使用表达式树构建动态查询的另一个示例。
2020年4月26日编辑
正如其他答案指出的那样,值得一试的是
System.Linq.Dynamic.Core库,它允许创建基于文本的动态查询。
这可能是在解决您的问题时利用表达式树最安全的方式。除非您是表达式树专家,否则最好使用适当的库,避免使用自制的“穷人”解决方案上线生产。
switch
语句替换它,这样看起来会容易一些 :) - Yeldar KurmangaliyevconditionName == "TitleDESC"
时,您是否打算按c.CategoriesTitle
排序,而在conditionName == "TitleASC"
时按c => c.Title
排序?这似乎是下面代码的错误复制和粘贴。此外,Code Review 可以作为这种问题的替代方案。 - Lance U. Matthews