Сопоставление по шаблонам Pattern

Функция Patterns доступна только начиная с версии Dart ≥ 3.0

I. Основные возможности Pattern

Pattern — отдельная синтаксическая категория Dart (равноправна с операторами и выражениями), которая описывает структуру значений. У нее две ключевые возможности, используемые одновременно: сопоставление (Match) и деструктуризация (Destructure).

  1. Сопоставление Match: проверка соответствия значения ожидаемой структуре
    • Совпадает ли с заданной константой
    • Принадлежит ли определенному типу
    • Имеет ли нужную структуру (List/Map/объект/запись Record)
    • Удовлетворяет ли дополнительному условию
  2. Деструктуризация Destructure: при успешном сопоставлении быстро извлекает поля и элементы значения, привязывая их к переменным

1. Пример сопоставления по константе

void main() {
	int number = 1;
	switch (number) {
	  case 1:
		print('one'); // Сопоставление по константе: number == 1
	  default:
		print('другое число');
	}
}Code language: Dart (dart)

Результат выполнения

one

2. Вложенные подшаблоны для сопоставления

Шаблоны поддерживают вложение подшаблонов для пошаговой проверки внутренней структуры

void main() {
  const a = 'a';
  const b = 'b';
  var obj = [ 'a', 'b'];
  switch (obj) {
    case [a, b]:
      print('$a, $b');
    default:
      print('совпадений нет');
  }
}Code language: Dart (dart)

Результат выполнения

a, b

3. Простой пример деструктуризации

var numList = [1, 2, 3];
// Деструктуризация списка через List-шаблон, привязка к новым переменным
var [x, y, z] = numList; 
print(x + y + z); // 6 x=1 y=2 z=3Code language: PHP (php)

4. Вложенное сопоставление + деструктуризация

void main() {
  var list = ['b', 99];
  switch (list) {
    // Логический ИЛИ-шаблон: первый элемент a или b, второй сохраняется в переменную c
    case ['a' || 'b', var c]:
      print(c); // 99
  }
}Code language: Dart (dart)

II. Частые сценарии использования Pattern

1. Объявление локальных переменных

Используется для деструктурирующего присваивания, позволяет создать сразу несколько переменных

// Деструктуризация вложенного шаблона Record + List
var (str, [n1, n2]) = ('text', [10, 20]);
print(str); //text
print(n1 + n2); //30Code language: Dart (dart)

2. Переприсвоение существующих переменных (обмен значений без временной переменной)

var (a, b) = ("лево", "право");
(b, a) = (a, b); // Обмен значений двух переменных
print('$a $b'); // право левоCode language: Dart (dart)

3. Ветвления switch / if-case (самое распространенное применение, защитное условие when)

В каждом case записываются опровержимые шаблоны: при отсутствии совпадения выполняется следующий case

Базовый вариант case для разных типов

void testSwitch(dynamic obj) {
  switch (obj) {
    case 1:
      print('число 1');
    case >= 10 && <= 20:
      print('число из диапазона 10–20');
    case (var a, var b):
      print('запись Record: $a,$b');
    default:
      print('подходящих вариантов нет');
  }
}Code language: Dart (dart)

Логический ИЛИ-шаблон: общий код для нескольких веток

enum Color { red, yellow, blue, green }
void main() {
  Color color = Color.red;
  bool isPrimary = switch (color) {
    Color.red || Color.yellow || Color.blue => true,
    _ => false,
  };
  print(isPrimary); // true
}
Code language: PHP (php)

Защитное условие when (дополнительная фильтрация после сопоставления по шаблону)

sealed class Shape {}
class Square { final double size; Square(this.size); }
class Circle { final double size; Circle(this.size); }

void checkShape(Shape shape) {
  switch (shape) {
    // Одновременно сопоставляем Square и Circle, добавляем условие size>0
    case Square(size: var s) || Circle(size: var s) when s > 0:
      print('корректная симметричная фигура');
    default:
      print('некорректная фигура');
  }
}Code language: Dart (dart)

Разница между when и обычным if

void compareWhen((int, int) pair) {
  switch (pair) {
    case (int a, int b):
      if (a > b) print('первое число больше');
      // Если условие if не выполняется — весь switch прерывается, последующие case не проверяются
    case (int a, int b) when a > b:
      print('【защита when】первое число больше');
      // Если условие when не выполняется — пропускаем только этот case и переходим к следующему
    case (int a, int b):
      print('первое число меньше или равно второму');
  }
}

void main() {
  print("===== Тест (5, 2) a > b =====");
  compareWhen((5, 2));

  print("\n===== Тест (2, 5) a < b =====");
  compareWhen((2, 5));
}Code language: Dart (dart)

4. Циклы for /for-in (удобная деструктуризация MapEntry)


void main() {
	Map<String, int> hist = {'a': 23, 'b': 100};
	
	// Полная запись
	for (var MapEntry(key: key, value: count) in hist.entries) {
	  print('$key : $count');
	}
	
	// Сокращенный синтаксис: key:key заменяется на :key
	for (var MapEntry(:key, value: count) in hist.entries) {
	  print('$key : $count');
	}
}Code language: Dart (dart)

Результат выполнения

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
a : 23
b : 100
a : 23
b : 100
Code language: CSS (css)

III. Практические сценарии применения Pattern в бизнесе

1: Несколько возвращаемых значений функции (деструктуризация Record)

Традиционный вариант (громоздкий код)

(String, int) userInfo() => ("Lily", 13);
void main() {
  var info = userInfo();
  var name = info.$1;
  var age = info.$2;
}
Code language: Dart (dart)

Упрощенная запись через шаблон

var (name, age) = userInfo();
// Деструктуризация именованного Record
final (:name, :age) = getData(); // Возвращает (name: "Doug", age:25)Code language: Dart (dart)

Конкретный пример


(String, int) userInfo() => ("Lily", 18);


({String name, int age}) getData() => (name: "Doug", age: 25);

void main() {
  {
    var (name, age) = userInfo();
    print("Имя: $name, возраст: $age");
  }

  // Внутренние переменные name/age не конфликтуют с внешними
  {
    final (:name, :age) = getData();
    print("Имя: $name, возраст: $age");
  }
}Code language: Dart (dart)

2: Деструктуризация экземпляров классов (объектный шаблон)

class Foo {
  final String one;
  final int two;
  Foo({required this.one, required this.two});
}

void main() {
  final Foo myFoo = Foo(one: "test", two: 66);
  var Foo(:one, :two) = myFoo;
  print('$one $two'); //test 66
}Code language: Dart (dart)

3: Алгебраические типы данных (запечатанные классы + вычисления через switch)

Единая обработка связанных подклассов без разрозненных методов экземпляра

import 'dart:math';

sealed class Shape {}
class Square implements Shape { final double length; Square(this.length); }
class Circle implements Shape { final double radius; Circle(this.radius); }

double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(radius: var r) => pi * r * r,
};
Code language: Dart (dart)

Полный пример реализации

import 'dart:math';

// Запечатанный родительский класс, подклассы можно определять только в этом файле
sealed class Shape {}

// Подкласс квадрата
class Square implements Shape {
  final double length;
  Square(this.length);
}

// Подкласс круга
class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

// Единый расчет площади через switch-выражение и объектный шаблон
double calculateArea(Shape shape) => switch (shape) {
  // Совпадение с Square, извлечение length в переменную l
  Square(length: var l) => l * l,
  // Совпадение с Circle, извлечение radius в переменную r
  Circle(radius: var r) => pi * r * r,
};

void main() {
  // Создаем экземпляры фигур
  Shape square = Square(5);
  Shape circle = Circle(3);

  // Рассчитываем площади
  double squareArea = calculateArea(square);
  double circleArea = calculateArea(circle);

  print("Площадь квадрата: $squareArea");    // 25.0
  print("Площадь круга: ${circleArea.toStringAsFixed(2)}"); // 28.27
}Code language: Dart (dart)

Результат выполнения

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Площадь квадрата: 25.0
Площадь круга: 28.27Code language: CSS (css)

4: Проверка и деструктуризация JSON (значительно упрощает вложенные условия)

(Без шаблонов) Многоуровневые проверки типов делают код громоздким

void oldCheck(Map data) {
  if (data is Map<String, Object?> && data.containsKey('user')) {
    var user = data['user'];
    if (user is List && user.length == 2) {
      String name = user[0] as String;
      int age = user[1] as int;
      print('$name $age');
    }
  }
}Code language: Dart (dart)

Шаблон if-case: одна строка для проверки структуры, типа и привязки переменных

void newCheck(Map data) {
  if (data case {'user': [String name, int age]}) {
    print('Пользователь $name, возраст $age');
  }
}Code language: Dart (dart)

Этот шаблон одновременно проверяет все пункты:

  1. data является Map и не равен null
  2. Присутствует ключ user
  3. Значение по ключу user — список из двух элементов
  4. Первый элемент строка String, второй целое число int
  5. Автоматическая привязка локальных переменных name и age

Пример реального использования

void main() {
  // 1. Корректная JSON структура: совпадение найдено
  final validJson = {
    'user': ['Lily', 13]
  };
  newCheck(validJson); // Вывод: Пользователь Lily, возраст 13

  // 2. Несоответствие типов в списке: возраст строка, совпадения нет
  final wrongTypeJson = {
    'user': ['Tom', '16']
  };
  newCheck(wrongTypeJson); // Вывода нет

  // 3. Список user неполный: нет возраста, совпадения нет
  final shortJson = {
    'user': ['Jack']
  };
  newCheck(shortJson); // Вывода нет

  // 4. Ключа user нет в данных: совпадения нет
  final noUserJson = {
    'info': ['Bob', 20]
  };
  newCheck(noUserJson); // Вывода нет
}

// Одношаговая проверка структуры/типа и деструктуризация через шаблон Map+List if-case
void newCheck(Map data) {
  if (data case {'user': [String name, int age]}) {
    print('Пользователь $name, возраст $age');
  } else {
    print('Неверный формат данных');
  }
}Code language: PHP (php)

Сопоставление по шаблонам Pattern

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *