Важный отказ от ответственности
В компьютерной науке всегда есть всегда .
Подход к этой проблеме, который я собираюсь предложить, требует повышения уровня разработки вашего решения. Это не всегда правильный подход, поскольку он увеличивает уровень абстракции вашего кода.
Если этот код не будет часто меняться, а число возможных критериев сортировки невелико и вы думаете, что он останется маленьким, вы можете жить счастливо и безопасно с вашей текущей реализацией (или эквивалентным решением на основе оператора switch, что, вероятно, лучше с точки зрения читабельности кода).
Зачем использовать Если каскад является проблемой
Помимо проблемы читабельности кода, основная проблема с длинным каскадом операторов if
- это нарушение принципа открытого закрытого типа. Здесь вы можете найти объяснение этого принципа.
Короче говоря, идея состоит в том, что код, подобный тому, который вы показали, подвержен постоянным изменениям, потому что каждый раз, когда вы вводите новое условие (или удаляете существующее), вы должны изменить существующий код путем добавления (или удаления) нового оператора if
. Это нежелательный сценарий, потому что изменение существующего кода, который работает, может привести к ошибкам и является дорогостоящей операцией с точки зрения времени и направленности. В желаемом сценарии есть фрагмент кода, который отлично работает, хорошо протестирован и может оставаться стабильным в течение времени. Чтобы выполнить новые требования, мы хотели бы написать новые модули (например, новые классы), не затрагивая ни один из существующих и работающих модулей.
Является ли решение каскада if оператором switch решением?
Нет, это не так. Преобразование каскада if в switch
может улучшить читаемость кода возможно, но это не сущность основной проблемы нарушения открытого закрытого принципа. На обслуживание оператора switch
влияет та же проблема, что и для каскада операторов if
.
Разработка шаблонов для спасения.
Первый подход - попытка применить следующий шаблон проектирования, который в основном является стратегическим шаблоном .
Обратите внимание, что, используя этот подход, вы расширяете функциональность вашей программы, просто написав новую реализацию интерфейса, но вам никогда не придется изменять существующие реализации интерфейса и код, потребляющий интерфейс.
// define an interface encapsualting the desired behavior
IProductsProvider
{
bool CanHandleCondition(string condition);
Task<IEnumerable<Products>> GetProducts(string condition);
}
// write an implementation of your interface for each possible condition that you have
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);
}
}
// this is the implementation of the interface that you will register with your DI container
// inject all the other implementations of the IProductsProvider interface
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);
}
}
// remember to register the class CompositeProvider as the implementation of the interface IProductsProvider in the DI container
// let the DI container to inject the implementation of IProductsProvider in your controller
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
.
Это решение не в полной мере применимо к вашей проблеме , потому что оно требует вызова метода generi c, и вы не можете вывести аргумент типа generi c из входных данных, поступающих из пользователь (имя свойства, используемого для сортировки продуктов), но я думаю, что деревья выражений стоит упомянуть здесь. Считайте эту часть моего ответа триггером для дальнейших исследований в мире деревьев выражений.
Следующий код является примером использования дерева выражений для получения делегата, используемого для сортировки некоторых объектов, начиная с название свойства, используемого для сортировки.
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 }
};
//imagine this comes from the user...
var propertyName = nameof(Person.Age);
// the issue is that here you need to pass the right generic type argument and based on my understanding you can't infer it from the propertyName variable
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; }
}
}
Любой член сообщества, который лучше меня понимает в дереве выражений topi c, может улучшить эту идею или объяснить, если и почему деревья выражений не подходят для сценария этого вопроса.
Здесь вы можете найти другой пример построения динамического c запроса с использованием дерева выражений.
Правка 26 апреля 2020 года
Как указано Из других ответов на те же вопросы стоит усилий, чтобы взглянуть на библиотеку System.Linq.Dynami c .Core , которая позволяет создавать динамические c запросы на основе текста.
Это, пожалуй, самый безопасный способ использовать деревья выражений для решения вашей проблемы. Если вы не являетесь экспертом в области дерева выражений, всегда лучше использовать надлежащие библиотеки и избегать производства с домашними решениями для «бедняков».