Стратегии функционального программирования Flutter для работы с итерируемыми объектами

Прочтите это, если хотите узнать о методах императивной парадигмы, ориентированной на Flutter.

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

Точно так же утомительно просматривать строки кода. Но вместо того, чтобы поднимать тяжести, я борюсь с нагрузкой технического долга. Дело в том, что вы должны сделать это как можно проще для себя. Для меня код, полученный в результате функционального программирования, прост. Правильное программирование должно радовать вас, когда вы смотрите на него, а функциональное программирование всегда вызывает у меня улыбку. Надеюсь вдохновить вас этой статьей. Давайте теперь посмотрим на 7 лучших методов функционального программирования, которые я чаще всего использую при написании кода!!! Мы сделаем это на примерах.

Определение: я буду использовать термин инструмент(ы) функционального программирования на протяжении всей статьи как — метод, используемый для стратегии функционального программирования.

Намерения обучения

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

Мы рассмотрим эти 7 лучших функциональных инструментов

1: .Карта

Инструмент .map преобразует список объектов в другой список.

[🟥].карта → [🔵]

2: .Где

Инструмент .where возвращает новый отложенный [Iterable] со всеми элементами, удовлетворяющими предикату [test].

[🟥 🟥 🟥 🟩 🟩].где(🟥) → [🟥 🟥 🟥]

3: .Развернуть

Инструмент .expand возвращает новый отложенный [Iterable] со всеми элементами в сведенном списке.

[[🟥 🟥 🟥] [🟩 🟩] [🟧]].расширить → [🟥 🟥 🟥 🟩 🟩 🟧]

4: .Возьмите

Инструмент .take возвращает ленивую итерацию из [count] первых элементов этой итерации.

[🟥 🟥 🟥 🟥 🟥 🟥].take(3) → [🟥 🟥 🟥]

5: .Содержит

Инструмент .contains определяет, содержит ли коллекция элемент, равный [element].

[🟥 🟥 🟥 🟥 🟥 🟥].содержит(🟥) → ✅

6: .Сложить

Инструмент .fold сокращает коллекцию до одного значения, последовательно комбинируя каждый элемент коллекции с текущим значением. то есть

[1️⃣, 2️⃣, 3️⃣, 2️⃣, 1️⃣].fold(➕) → 1️⃣➕2️⃣➕3️⃣➕2️⃣➕1️⃣ → 9️⃣

7: .Сгенерировать

Инструмент .generate создает список значений.

[🟥 🟩 🟧].генерировать(✖️3️⃣)→ [🟥 🟥 🟥 🟩 🟩 🟩 🟧 🟧 🟧]

Пожалуйста, прокрутите вниз, чтобы увидеть дальнейшее обсуждение этих инструментов.

Раздел первый: демонстрационное приложение

Давайте углубимся в это. Пожалуйста, перейдите по следующей ссылке на YouTube для быстрой демонстрации функциональных возможностей приложения.



Найдите код GitHub здесь.

Для этого минимального приложения представьте, что мы разрабатываем новую игру, в которой используются жетоны, извлеченные из кошелька для монет, чтобы определить результат действий персонажа. У персонажа может быть четыре случайных ответа на действие: бой, бегство, заморозка и удача. Например, в нашем коде у персонажа есть заморозка: 1, полет: 2, бой: 3 и удача: 1 количество жетонов. Таким образом, всего 1 + 2 + 3 + 1 = 7 жетонов доступны для розыгрыша для действия.

Нам нужен код для обработки:

  1. Берите по три жетона за одно действие.
  2. Жетон боя зеленый, полет синий, заморозка фиолетовая, а удача серая.
  3. Каждый токен имеет значение (1 — наименьшее), которое может достигать количества типов ответов без повторяющихся значений (т. или 2, а три жетона боя имеют значение 1–3).
  4. Отображаемое значение жетона представляет собой сумму всех выпавших ответов этого типа (т. е. если игрок вытянул два жетона боя со значением 1 и 3, отображаемая сумма равна 4).
  5. Каждый жетон имеет лицевую сторону с изображением орла или решки (предположим, что мы вытягиваем жетон и переворачиваем его для получения результата).
  6. Жетоны орлов имеют желтую окантовку, а решки — черные.
  7. Если жетон удачи выпал орлом, он дает 1 или -1 за решку.
  8. Нажатие плавающей кнопки действия выполняет другое действие, рисует три новых токена и заменяет результаты пользовательского интерфейса.

Начальный код

Найдите код GitHub здесь.

Этот код прост. Код ведет к StatefulWidget, который содержит Scaffold. В Scaffold есть виджет Wrap и кнопка FloatingActionButton. Обертка содержит четыре пользовательских виджета Token. Каждый виджет Token отличается передачей переменной значения (описание 4), вероятности (описание 6) и цвета (описание 2) в его конструктор. FloatingActionButton вызовет новый рисунок и заменит предыдущие значения сборкой пользовательского интерфейса с использованием setState.

import 'package:flutter/material.dart';
import 'package:test_token_research/models.dart';
import 'package:test_token_research/old_strategy_fx.dart';
import 'package:test_token_research/helper_functions.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

// 1) We are going to get data from here.
  var result = oldResultsOfDraw(getDrawNumber(), getResponses());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: Wrap(children: [
// 2) Needs DRY
            Token(
              value: result['fight total'],
              color: Colors.green,
              chances: result['fight faces'],
            ),
            Token(
              value: result['flight total'],
              color: Colors.blue,
              chances: result['flight faces'],
            ),
            Token(
              value: result['freeze total'],
              color: Colors.purple,
              chances: result['freeze faces'],
            ),
            Token(
              value: result['luck total'],
              color: Colors.grey,
              chances: result['luck faces'],
            ),
          ]),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
// 3) We are going to reset result here.
            result = oldResultsOfDraw(getDrawNumber(), getResponses());
          });
        },
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

class Token extends StatelessWidget {
  const Token({
    super.key,
    required this.value,
    required this.chances,
    required this.color,
  });

  final int value;
  final List<Coin> chances;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return CircleAvatar(
      radius: 35,
// 4) getChangeColor is a helper function.
      backgroundColor: getChanceColor(chances),
      child: CircleAvatar(
        radius: 30,
        backgroundColor: color,
        child: Text(value.toString(), style: const TextStyle(fontSize: 20)),
      ),
    );
  }
}

Есть много функций, на которые нам нужно обратить внимание, чтобы получить нужную нам информацию.

Функции: получение результатов розыгрыша

Методы 1) и 3) идентичны и требуют рефакторинга Don’t Repeat Yourself. Но в этой статье я больше заинтересован в том, чтобы сделать код максимально простым и вместо этого сосредоточиться на функциональном программировании.

// 1)
var result = getResultsOfDraw(getDrawNumber(), getResponses());

// 3) 
result = getResultsOfDraw(getDrawNumber(), getResponses());

Давайте сначала рассмотрим наше намерение получить результаты ничьей.

getResultsOfDraw() {
  /* 
    1) get a list of each type of response of the character.
    2) transform 1) to a list of individual type's with values.
    3) transform 2) to a list of all the tokens has all of the data needed.
    4) shuffle 3).
    5) get a list of the first 3 tokens of 4).
    6) get detailed information for each of the tokens of 5).
    7) return the results of 6).
  */
}

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

getResultsOfDraw(int drawNumber, List<Type> responses) {
  /* 1) get a list of each type of response of the character.
        This will be done using the responses variable.
  */
}

Из приведенного выше кода мы передаем список типов. Мне нравится хранить типы вместо созданных классов, потому что у них меньше технического долга. Позже в этой статье я покажу отличный способ создания экземпляров этих типов. Список типов ответов был жестко закодирован в этом примере кода, чтобы избежать сложности. getResponses является вспомогательным методом и находится в другом месте кода.

// List of Responses.
List<Type> getResponses() =>
              [Freeze, Flight, Flight, Fight, Fight, Fight, Luck];
// getResultsOfDraw is a helper function.
var result = getResultsOfDraw(getDrawNumber(), getResponses());
// and
setState(() { 
  result = getResultsOfDraw(getDrawNumber(), getResponses());
});

Давайте рассмотрим модели. Пять классов унаследованы от Response: Fight, Flight, Freeze, Luck и Default. Класс Response скудно настроен и имеет два миксина: Value и CoinFace, чтобы придать ему некоторые необходимые значения. Миксин CoinFace содержит перечисление Coin с орлом или решкой.

class Response with Value, CoinFace {}

mixin Value {
  int value = 0;
}

mixin CoinFace {
  Coin face = Coin.tails;
}

enum Coin { heads, tails }

class Fight extends Response {}

class Flight extends Response {}

class Freeze extends Response {}

class Luck extends Response {}

class Default extends Response {}

Функциональное программирование блещет коллекциями, потому что есть много инструментов для работы с ними. Например, в этой игре мы вытягиваем три случайных жетона из списка семи. Это может быть любая комбинация, и нам нужны детали этих комбинаций, чтобы игра работала. Итак, идея состоит в том, чтобы

  1. работа с ответами персонажа.
  2. Настройте ответы со значимой информацией.
  3. Верните результаты.

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

List<Type> getResponses() =>
              [Freeze, Flight, Flight, Fight, Fight, Fight, Luck];

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

// hypothetical distribution of Types
[Fight, Freeze, Flight, Fight, Luck, Fight, Flight];

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

getResultsOfDraw(List<Type> responses) {
  // 1) get a list of each type of response of the character.
 List<Type> setOfTypes = responses.toSet().toList();
}
/// flutter: setOfTypes: [Freeze, Flight, Fight, Luck]

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

getResultsOfDraw(int drawNumber, List<Type> responses) {
   // 1) get a list of each type of response of the character.
   List<Type> setOfTypes = responses.toSet().toList();

   // 2) transform 1) to a list of individual type's with values.
   // returns a list with the correct number of instantiated Response of
   // of a specific Type.
        List<Response> getSkill(List<Type> responses, Type type) {
        int count = responses.where((element) => element == type).length;
        return [
          for (var i = 0; i < count; i++)
            convertToConcreteType(type)
              ..value = i + 1
              ..face = headsOrTails()
        ];
      }

   // 3) transform 2) to a list of all the tokens has all of the data needed.
        List<Response> valuedListOfTokens = setOfTypes
          .map((type) => getSkill(responses, type))
          .expand((element) => element)
          .toList();
}

Response convertToConcreteType(response) {
  switch (response) {
    case Freeze:
      return Freeze();
    case Flight:
      return Flight();
    case Fight:
      return Fight();
    case Luck:
      return Luck();
    default:
      return Default();
  }
}

Coin headsOrTails() => Random().nextBool() ? Coin.heads : Coin.tails;

/// flutter: type: Freeze, value: 1, face: Coin.heads
/// flutter: type: Flight, value: 1, face: Coin.heads
/// flutter: type: Flight, value: 2, face: Coin.heads
/// flutter: type: Fight, value: 1, face: Coin.tails
/// flutter: type: Fight, value: 2, face: Coin.heads
/// flutter: type: Fight, value: 3, face: Coin.heads
/// flutter: type: Luck, value: 1, face: Coin.heads

Стратегия функционального программирования 1: .Map

Инструмент .map преобразует список объектов в другой список.

[🟥].карта → [🔵]

Начнем с первого взгляда на 3). Именно в этой части кода происходит тяжелая работа. В нашем случае он создает List‹Response› из List ‹ Type›. setOfTypes.map циклически перебирает каждый элемент, который у него есть, и выполняет функцию для этого элемента. В этом случае применяется 2) метод getSkills, который использует второй инструмент, .where.

Стратегия функционального программирования 2: .Where

Инструмент .where возвращает новый отложенный [Iterable] со всеми элементами, удовлетворяющими предикату [test].

[🟥 🟥 🟥 🟩 🟩].где(🟥) → [🟥 🟥 🟥]

Итак, в 2) он ищет тип, переданный .map, который является одним типом. Инструмент where ищет сохраненные значения этого типа, добавляет их в список, а затем возвращает целое число, длину в этом списке. Затем применяется цикл for для создания списка экземпляров классов правильного типа с использованием вспомогательных функций convertToConcreteType и headOrTails.

Метод convertToConcreteType содержит жестко запрограммированный оператор switch для возврата определенного экземпляра правильного типа. Затем цикл for добавляет допустимое числовое значение, а headOrTails присваивает ему случайное значение перечисления.

Важно понимать, что каждая итерация .map и метода getSkill приводит к добавлению списка‹Response› в другой список. Таким образом, мы получаем вложенный список.

[🟥,🟩] → [[🟥(1, решка),🟥(2, решка)], [🟩(1, решка)]]

Вложенные списки усложняют наш код, и с ними может быть сложно работать, поэтому вместо того, чтобы иметь дело с вложенным списком, давайте вместо этого сгладим его, используя:

Стратегия функционального программирования 3. Расширение

Инструмент .expand возвращает новый отложенный [Iterable] со всеми элементами в сведенном списке.

[[🟥 🟥 🟥] [🟩 🟩] [🟧]].расширить → [🟥 🟥 🟥 🟩 🟩 🟧]

ValuedListOfTokens.shuffle не является чисто инструментом функционального программирования.

Мы не считаем .shuffle чистой функцией. Функциональное программирование возвращает что-то из вызова. Если вы не можете создать новую переменную из вызова функции, это не чистая функция. Shuffle возвращает пустоту. Чистая функция вернет то же значение из переданной информации. Вызов .shuffle обычно каждый раз возвращает что-то новое. Эта ненадежность не поддается предсказуемому тестированию и нежелательна. Тем не менее, это полезно для этого контекста.

getResultsOfDraw(int drawNumber, List<Type> responses) {
   List<Type> setOfTypes = responses.toSet().toList();

   // 2) transform 1) to a list of individual type's with values.
        List<Response> getSkill(List<Type> responses, Type type) {
        int count = responses.where((element) => element == type).length;
        return [
          for (var i = 0; i < count; i++)
            convertToConcreteType(type)
              ..value = i + 1
              ..face = headsOrTails()
        ];
      }

   // 3) transform 2) to a list of all the tokens has all of the data needed.
        List<Response> valuedListOfTokens = setOfTypes
          .map((type) => getSkill(responses, type))
          .expand((element) => element)
          .toList();
}

  // 4) shuffle 3).
      valuedListOfTokens.shuffle();
  // 5) get a list of the first 3 tokens of 4).
  // 6) get detailed information for each of the tokens of 5).
  // 7) return the results of 6).


Стратегия функционального программирования 4: .Take

Инструмент .take возвращает ленивую итерацию из [count] первых элементов этой итерации.

[🟥 🟥 🟥 🟥 🟥 🟥].take(3) → [🟥 🟥 🟥]

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

getResultsOfDraw(int drawNumber, List<Type> responses) {
   List<Type> setOfTypes = responses.toSet().toList();

   // 2) transform 1) to a list of individual type's with values.
        List<Response> getSkill(List<Type> responses, Type type) {
        int count = responses.where((element) => element == type).length;
        return [
          for (var i = 0; i < count; i++)
            convertToConcreteType(type)
              ..value = i + 1
              ..face = headsOrTails()
        ];
      }

   // 3) transform 2) to a list of all the tokens has all of the data needed.
        List<Response> valuedListOfTokens = setOfTypes
          .map((type) => getSkill(responses, type))
          .expand((element) => element)
          .toList();
}

  // 4) shuffle 3).
      valuedListOfTokens.shuffle();
  // 5) get a list of the first 3 tokens of 4).
      var tokens = valuedListOfTokens.take(drawNumber);
  // 6) get detailed information for each of the tokens of 5).
  // 7) return the results of 6).

Стратегия функционального программирования 5: .Contains

Инструмент .contains определяет, содержит ли коллекция элемент, равный [element].

[🟥 🟥 🟥 🟥 🟥 🟥].содержит(🟥) → ✅

Этот функциональный инструмент удобен для пользователя благодаря невероятному пониманию, которое он дает в отношении коллекций. Многие другие стратегии были бы лучше для использования списка токенов, а не для создания переменной для каждого типа, но я решил сделать это таким образом, чтобы преувеличить использование .contains. И хотя шаги 5) и 6) не идеальны для передового опыта, они отлично подходят для демонстрации функциональных инструментов.

getResultsOfDraw(int drawNumber, List<Type> responses) {
   List<Type> setOfTypes = responses.toSet().toList();

   // 2) transform 1) to a list of individual type's with values.
        List<Response> getSkill(List<Type> responses, Type type) {
        int count = responses.where((element) => element == type).length;
        return [
          for (var i = 0; i < count; i++)
            convertToConcreteType(type)
              ..value = i + 1
              ..face = headsOrTails()
        ];
      }

   // 3) transform 2) to a list of all the tokens has all of the data needed.
        List<Response> valuedListOfTokens = setOfTypes
          .map((type) => getSkill(responses, type))
          .expand((element) => element)
          .toList();
}

  // 4) shuffle 3).
      valuedListOfTokens.shuffle();
  // 5) get a list of the first 3 tokens of 4).
      var tokens = valuedListOfTokens.take(drawNumber);
  // 6) get detailed information for each of the tokens of 5).
      var runs =
      tokens.where((element) => tokens.contains(Freeze()) || element is Freeze);
      var hides =
      tokens.where((element) => tokens.contains(Flight()) || element is Flight);
      var fights =
      tokens.where((element) => tokens.contains(Fight()) || element is Fight);
      var lucks =
      tokens.where((element) => tokens.contains(Luck()) || element is Luck);
  // 7) return the results of 6).

Стратегия функционального программирования 6: .Fold

Инструмент .fold сокращает коллекцию до одного значения, последовательно комбинируя каждый элемент коллекции с текущим значением. то есть

[1️⃣, 2️⃣, 3️⃣, 2️⃣, 1️⃣].fold(➕) → 1️⃣➕2️⃣➕3️⃣➕2️⃣➕1️⃣ → 9️⃣

В этом примере в 7) мы видим примеры использования .fold, который суммирует все значения вместе. Используя класс, мы могли бы улучшить Map‹String, dynamic›. Тем не менее, я хотел, чтобы это было просто, и нет ничего более простого, чем карта.

getResultsOfDraw(int drawNumber, List<Type> responses) {
   List<Type> setOfTypes = responses.toSet().toList();

   // 2) transform 1) to a list of individual type's with values.
        List<Response> getSkill(List<Type> responses, Type type) {
        int count = responses.where((element) => element == type).length;
        return [
          for (var i = 0; i < count; i++)
            convertToConcreteType(type)
              ..value = i + 1
              ..face = headsOrTails()
        ];
      }

   // 3) transform 2) to a list of all the tokens has all of the data needed.
        List<Response> valuedListOfTokens = setOfTypes
          .map((type) => getSkill(responses, type))
          .expand((element) => element)
          .toList();
}

  // 4) shuffle 3).
      valuedListOfTokens.shuffle();
  // 5) get a list of the first 3 tokens of 4).
      var tokens = valuedListOfTokens.take(drawNumber);
  // 6) get detailed information for each of the tokens of 5).
      var runs =
      tokens.where((element) => tokens.contains(Freeze()) || element is Freeze);
      var hides =
      tokens.where((element) => tokens.contains(Flight()) || element is Flight);
      var fights =
      tokens.where((element) => tokens.contains(Fight()) || element is Fight);
      var lucks =
      tokens.where((element) => tokens.contains(Luck()) || element is Luck);
  // 7) return the results of 6).
      return {
    'flight': runs.length,
    'flight values': runs.map((e) => e.value).toList(),
    'flight faces': runs.map((e) => e.face).toList(),
    'flight total': runs.fold(0, (sum, element) => sum + element.value),
    'freeze': hides.length,
    'freeze values': hides.map((e) => e.value).toList(),
    'freeze faces': hides.map((e) => e.face).toList(),
    'freeze total': hides.fold(0, (sum, element) => sum + element.value),
    'fight': fights.length,
    'fight values': fights.map((e) => e.value).toList(),
    'fight faces': fights.map((e) => e.face).toList(),
    'fight total': fights.fold(0, (sum, element) => sum + element.value),
    'luck': lucks.length,
    'luck values': lucks.map((e) => e.value).toList(),
    'luck faces': lucks.map((e) => e.face).toList(),
    'luck total': lucks.fold(
        0,
        (sum, element) => (element.face == Coin.heads)
            ? sum + element.value
            : sum - element.value),
  };

Мы можем реорганизовать функцию getSkill и код переменной tokensWithValues ​​в приведенном выше коде.

Стратегия функционального программирования 7. Генерация

Инструмент .generate создает список значений.

[🟥 🟩 🟧].генерировать(✖️3️⃣)→ [🟥 🟥 🟥 🟩 🟩 🟩 🟧 🟧 🟧]

В совокупности функциональные программисты не считают использование циклов в функциональном программировании идеальным.

Мы не используем циклы For в функциональном программировании. Вместо этого мы используем функции более высокого порядка, такие как карта, фильтр, уменьшение и т. д. Они подходят для перебора массива.

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

List<Response> getSkill(List<Type> responses, Type type) {
        int count = responses.where((element) => element == type).length;
        return [
          for (var i = 0; i < count; i++)
            convertToConcreteType(type)
              ..value = i + 1
              ..face = headsOrTails()
        ];
      }

Вместо этого используйте .generate, чтобы избежать использования цикла For.

List<Response> getSkill(List<Type> responses, Type type) {
    return List<Response>.generate(
      responses.where((element) => element == type).length,
      (index) => convertToConcreteType(type)
        ..value = index + 1
        ..face = headsOrTails(),
    );
  }

Точная настройка рефакторинга

К другой теме. Мы можем выполнить рефакторинг вхождений .map, за которым следует .expand, просто рефакторинг его только для расширения.

Например, предыдущий код:

List<Response> valuedListOfTokens = setOfTypes
          .map((type) => getSkill(responses, type))
          .expand((element) => element)
          .toList();

Вместо этого мы можем реорганизовать это без потери функциональности:

List<Response> valuedListOfTokens =
      setOfTypes.expand((type) => getSkill(responses, type)).toList();

Облегченные шаблоны стратегии

Другой пример использования функции .where возникает, когда мы хотим определить цвета границ наших токенов, найденных в main.dart. Некоторые онлайн-ресурсы начали называть такого рода применение инструмента .where упрощенным шаблоном стратегии, который требует гораздо меньших когнитивных издержек по сравнению со своим старшим родственником.

// 4) getChangeColor is a helper function.
      backgroundColor: getChanceColor(chances),

Color getChanceColor(luckTokens) {
  return (luckTokens.where((e) => e == Coin.heads).length >
          luckTokens.where((e) => e == Coin.tails).length)
      ? Colors.yellow
      : Colors.black;
}

Рефакторинг кода DRY с использованием перечислений и функционального программирования

Я оценил сложность Dart Enums. Я предпочитаю использовать их в первую очередь, а не другие шаблоны проектирования, потому что они просты в использовании. Я думаю о программировании как о многоэтапном процессе, подобном строительству дороги. Города не начинают строить десятиполосные шоссе; они начинаются с однополосных улиц и добавляются к ним по мере необходимости. Итак, в моем подходе я начинаю с перечисления, а затем при необходимости преобразую его в шаблоны проектирования. Кристиан Клаузен еще больше укрепил мою веру в свою книгу Пять строк кода (2021 г.), в которой показано, как быстро и эффективно провести рефакторинг от перечислений к шаблонам проектирования.

Давайте посмотрим, как реорганизовать следующий код, используя перечисления Dart и функциональное программирование.

Возьмем следующий код. Сначала обратите внимание на следующее:

//main.dart

Map<String, dynamic> result = resultsOfDraw(getDrawNumber(), getResponses());

Wrap(children: [
            Token(
              value: result['fight total'],
              color: Colors.green,
              chances: result['fight faces'],
            ),
            Token(
              value: result['flight total'],
              color: Colors.blue,
              chances: result['flight faces'],
            ),
            Token(
              value: result['freeze total'],
              color: Colors.purple,
              chances: result['freeze faces'],
            ),
            Token(
              value: result['luck total'],
              color: Colors.grey,
              chances: result['luck faces'],
            ),
          ]),

Мы можем удалить повторение в этом коде, применив этот рефакторинг:

Wrap(children: [
            ...Attribute.values
                .map((attribute) => Token(
                    value: result[attribute.valueID],
                    chances: result[attribute.chanceID],
                    color: attribute.color))
                .toList(),
          ]),

enum Attribute {
  fight('fight total', Colors.green, 'fight faces'),
  flight('flight total', Colors.blue, 'flight faces'),
  freeze('freeze total', Colors.purple, 'freeze faces'),
  luck('luck total', Colors.grey, 'luck faces');

  const Attribute(this.valueID, this.color, this.chanceID);
  final String valueID;
  final Color color;
  final String chanceID;
}

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

Заключение

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

Найдите код GitHub здесь.

Интересуют четыре файла: main.dart, helper_functions.dart, models.dart и Strategy_fx.dart.