Если вы занимались программированием какое-то время, вы, возможно, встречали функции более высокого порядка, но (как и я), возможно, не до конца осознавали, насколько мощными они могут быть. Если вы не слышали о них раньше, не бойтесь, у меня есть объяснение ниже.

Что такое функции высшего порядка?

Проще говоря, функция высшего порядка — это функция, вход или выход которой также являются функцией.

В следующем примере функция Map принимает список определенного типа данных и функцию и возвращает новый список с функцией, примененной к каждому из элементов в списке (в основном эквивалентно методу LINQs Select).

// definition
List<TOut> Map<TIn, TOut>(List<TIn> list, Func<TIn, TOut> mapper)
{
    var newList = new List<TOut>();
    foreach (var item in list)
    {
        var newItem = mapper(item);
        newList.Add(newItem);
    }

    return newList;
}

// usage
var myList = new List<int> { 1, 2, 3, 4, 5 };

int multiplyBy2 (int num) => num * 2;
var multipliedList = Map(myList, multiplyBy2);
// output { 2, 4, 6, 8, 10 };

bool isEven (int num) => num % 2 == 0;
var isEvenList = Map(myList, isEven);
// output { false, true, false, true, false };

 

Вот пример функции более высокого порядка, которая возвращает функцию. Сначала вы вызываете функцию Add с заданным целым числом, которая возвращает функцию, которая добавляет это первое целое число к любому заданному другому целому числу.

Func<int, int> Add(int a) => (int b) => a + b;

var add9 = Add(9);

var sum1 = add9(1);
// output 10

var sum2 = add9(2);
// output 11

Надеюсь, теперь вы понимаете, почему функции высшего порядка так полезны; они позволяют многократно использовать сложный код очень гибким образом, позволяя клиенту определять свои собственные функции ввода / вывода. Например, без использования функций высшего порядка нужно было бы определить новую функцию Map для каждого отдельного типа отображения, в котором они нуждались.

Практический пример — Repository Pattern

Repository Pattern — это очень распространенный шаблон проектирования, используемый в качестве абстракции доступа к данным. Это позволяет выполнять типичные операции CRUD без необходимости прямого взаимодействия клиента с поставщиком данных. Типичный интерфейс репозитория может выглядеть примерно так:

public interface IProductRepository
{
    int Create(Product product);
    bool Update(Product product);
    bool Delete(int id);
    Product GetById(int id);
    IEnumerable<Product> GetAll();
    IEnumerable<Product> GetByCategoryId(int categoryId);
    IEnumerable<Product> GetActive();
    // etc...
}

Как вы можете видеть в этом типичном примере, существует множество определений, которые все возвращают IEnumerable <Product>, и каждый раз, когда вам нужен другой конкретный тип фильтра продукта, который требует добавления в интерфейс и написания совершенно новой реализации. Это не очень хорошо для обслуживания кода…

Вместо этого, используя функции более высокого порядка, мы можем определить единственный метод, который получает IEnumerable <Product>, который принимает функцию фильтра в качестве входных данных. Таким образом, клиент может определять свои собственные фильтры, и ProductRepository не нуждается в постоянном обновлении с новыми реализациями.

interface IProductRepository
{
    // create, update, delete omitted

    IEnumerable<Product> Get(Func<Product, bool> filter = null);
}

public class ProductRepository : IProductRepository
{
    private readonly List<Product> _products = new List<Product>(); // data source

    public IEnumerable<Product> Get(Func<Product, bool> filter = null)
    {
        // typically you might use the LINQ Where method here
        // but using the foreach to be clear what is happening

        if (filter is null) return _products;     

        var filteredList = new List<Product>();
        foreach(var product in _products)
        {
            if(filter(product))
            {
                filteredList.Add(product);
            }
        }

        return filteredList;
    }
}
// client code
var allProducts = _productRepository.Get();
var productsByCategoryId = _productRepository.Get(p => p.CategoryId == 1);
var activeProducts = _productRepository.Get(p => p.Active);

Вывод

В этой статье я представил концепции функций высшего порядка и продемонстрировал, насколько они чрезвычайно полезны при создании многоразового и гибкого кода, который легко поддерживать и понимать. Я также привел практический пример использования, который вы можете найти в корпоративном приложении .NET, — делая шаблон репозитория более пригодным для повторного использования.