Зоопарк функций: часть 3 — команда, перегрузка, каррирование

Часть1 — Часть 2 — Часть 3

Это следующая статья о расширении функций. И сегодня мы поговорим о паттерне команды и расширениях с двумя аргументами: перегрузкой и каррированием.

7. Команда

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

Имея это в виду, мы также можем передать имя метода в качестве аргумента. Ну, на самом деле это довольно просто, это будет работать так: допустим, у нас есть общий метод send, который принимает имя метода и остальные аргументы. Тогда код будет выглядеть так:

const Zoo = {
  // Regular methods
  getAnimal(type, color) {
    const animals = {
      Frog: {
        'Infra Red': 'ir_frog.jpg',
        'Caribbean Green': 'cr_frog.jpg',
      },
      Fish: {
        'Infra Red': 'ir_fish.jpg',
        'Caribbean Green': 'cr_fish.jpg',
      },
    };
    return animals[type][color];
  },
getColors() {
    return ['Infra Red', 'Caribbean Green'];
  },
getTypes() {
    return ['Frog', 'Fish'];
  },
send(methodName, ...arguments) {
    return this[methodName](...arguments);
  },
};
console.log(Zoo.send('getTypes'));
// => [ 'Frog', 'Fish' ]
console.log(Zoo.send('getAnimal', 'Fish', 'Infra Red')); 
// => 'ir_fish.jpg'

Это называется Шаблон команды. Это может быть полезно во многих случаях. Когда вы разрабатываете пользовательский интерфейс, его можно использовать для создания кнопок отмены/возврата, когда вы хотите регистрировать все действия, которые были вызваны. На самом деле даже Redux также основан на шаблоне команд.

Теперь с ES6 Proxy вы можете сделать шаблон команды более красивым:

const nextZoo = new Proxy(Zoo, {
  get(target, propKey) {
    console.log(propKey);
    return target[propKey];
  },
});
console.log(nextZoo.getColors());
// => getColors
// => [ 'Infra Red', 'Caribbean Green' ]

8. Перегруженная функция

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

Например, предположим, что у нас есть функция, которая вычисляет квадраты чисел, например:

function numberSquare(number) {
  return number * number;
}
console.log(numberSquare(2)); // => 4

Теперь, если бы мы хотели иметь функцию, которая также могла бы вычислять квадраты каждого числа массива, мы могли бы сделать это следующим образом:

function arraySquare(array) {
  return array.map(numberSquare);
}
function square(numberOrArray) {
  if (Array.isArray(numberOrArray)) 
    return arraySquare(numberOrArray);
  else 
    return numberSquare(numberOrArray);
}
console.log(square(2)); // => 4
console.log(square([1, 2, 3])); // => [ 1, 4, 9 ]

В большинстве случаев мне нравится делать функции как можно более простыми, и я позволяю им принимать только один тип аргументов. Это выгодно для минимизации затрат на техническое обслуживание и общей производительности. Таким образом, обычно перегрузка функций не рекомендуется. Хотя в некоторых случаях это может быть очень полезно, особенно для рекурсивных функций, когда вы деструктурируете объекты и массивы.

9. Каррированная функция

Каррированные функции уже много обсуждались всеми. Для тех, кто пропустил это, в основном каррированные функции позволяют принимать аргументы по одному. В простом случае это работает так:

function multiply(a, b) {
  if (b) return a * b;
  return function(b) {
    return a * b;
  };
}
const double = multiply(2);
console.log(multiply(2, 3)); // => 6
console.log(multiply(2)(3)); // => 6
console.log(double(3)); // => 6

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

На этом я завершаю серию статей о паттернах расширения функций. Если кто-нибудь прочитает это, дайте мне знать, если у вас есть идеи о других типах функций.

Удачи в ваших приключениях по программированию, ребята! █