← Назад к вопросам

Что такое ELT в Dart?

1.3 Junior🔥 171 комментариев
#Dart

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое ELT (Extension on Type) в Dart

Определение

ELT (Extension on Type) — это механизм в Dart, который позволяет добавлять новые методы и свойства к существующим классам без их изменения и без наследования. Введены в Dart 2.7 и являются мощным инструментом для расширения функциональности встроенных и сторонних типов.

Официально это называется Extension Methods или Extensions, но аббревиатура ELT часто используется в контексте расширений на типы.

Синтаксис

extension NameOfExtension on TypeToExtend {
  // Новые методы
  // Новые свойства (getter/setter)
}

Пример 1: Расширение встроенного типа String

Без расширения - много кода:

bool isValidEmail(String email) {
  final emailRegex = RegExp(
    r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
  );
  return emailRegex.hasMatch(email);
}

String capitalize(String text) {
  if (text.isEmpty) return text;
  return text[0].toUpperCase() + text.substring(1);
}

String removeWhitespace(String text) {
  return text.replaceAll(' ', '');
}

void main() {
  final email = "john@example.com";
  if (isValidEmail(email)) { // Функция вместо метода
    print(capitalize("hello"));
  }
}

С расширением - чистый код:

extension StringValidation on String {
  bool isValidEmail() {
    final emailRegex = RegExp(
      r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    );
    return this.contains('@') && emailRegex.hasMatch(this);
  }
  
  bool isValidPhoneNumber() {
    return RegExp(r'^\+?[1-9]\d{1,14}$').hasMatch(this);
  }
}

extension StringFormatting on String {
  String capitalize() {
    if (isEmpty) return this;
    return this[0].toUpperCase() + substring(1);
  }
  
  String removeWhitespace() => replaceAll(' ', '');
  
  String toTitleCase() {
    return split(' ')
        .map((word) => word.capitalize())
        .join(' ');
  }
}

void main() {
  final email = "john@example.com";
  if (email.isValidEmail()) { // Метод на самом объекте!
    print("hello".capitalize());
  }
  
  print("john doe".toTitleCase()); // John Doe
  print("+1-234-567-8900".isValidPhoneNumber()); // true
}

Пример 2: Расширение на int

extension IntUtilities on int {
  // Повторить строку N раз
  String repeat(String text) => text * this;
  
  // Проверить четность
  bool get isEven => this % 2 == 0;
  
  bool get isOdd => this % 2 != 0;
  
  // Fibonacci
  int get fibonacci {
    if (this <= 0) return 0;
    if (this <= 1) return 1;
    int a = 0, b = 1;
    for (int i = 2; i <= this; i++) {
      int temp = a + b;
      a = b;
      b = temp;
    }
    return b;
  }
  
  // Форматировать как деньги
  String toCurrency(String symbol = '\$') {
    return '$symbol${toString().replaceAllMapped(
      RegExp(r'\B(?=(\d{3})+(?!\d))'),
      (Match m) => ',',
    )}';
  }
}

void main() {
  print(3.repeat('Hello ')); // Hello Hello Hello
  print(4.isEven); // true
  print(5.isOdd); // true
  print(10.fibonacci); // 55
  print(1000000.toCurrency('₽')); // ₽1,000,000
}

Пример 3: Расширение на List

extension ListUtilities<T> on List<T> {
  // Безопасный доступ по индексу
  T? getOrNull(int index) {
    if (index >= 0 && index < length) {
      return this[index];
    }
    return null;
  }
  
  // Получить случайный элемент
  T random() {
    import 'dart:math';
    return this[Random().nextInt(length)];
  }
  
  // Разделить на подсписки
  List<List<T>> chunked(int size) {
    final chunks = <List<T>>[];
    for (int i = 0; i < length; i += size) {
      chunks.add(sublist(i, i + size > length ? length : i + size));
    }
    return chunks;
  }
  
  // Удалить дубликаты
  List<T> unique() => toSet().toList();
}

class User {
  final String name;
  User(this.name);
}

void main() {
  final numbers = [1, 2, 3, 4, 5];
  print(numbers.getOrNull(10)); // null
  print(numbers.random()); // случайное число
  
  final chunks = numbers.chunked(2);
  print(chunks); // [[1, 2], [3, 4], [5]]
  
  final items = [1, 1, 2, 2, 3, 3];
  print(items.unique()); // [1, 2, 3]
}

Пример 4: Расширение на DateTime

extension DateTimeUtilities on DateTime {
  // Начало дня
  DateTime get startOfDay {
    return DateTime(year, month, day);
  }
  
  // Конец дня
  DateTime get endOfDay {
    return DateTime(year, month, day, 23, 59, 59, 999);
  }
  
  // Является ли сегодня
  bool get isToday {
    final now = DateTime.now();
    return year == now.year && month == now.month && day == now.day;
  }
  
  // Дней назад/вперед
  DateTime addDays(int days) {
    return add(Duration(days: days));
  }
  
  // Форматирование (локализация)
  String toFormattedString() {
    return '$day.$month.$year';
  }
}

void main() {
  final now = DateTime.now();
  print(now.startOfDay); // 2024-01-15 00:00:00
  print(now.endOfDay); // 2024-01-15 23:59:59
  print(now.isToday); // true
  
  final tomorrow = now.addDays(1);
  print(tomorrow.toFormattedString()); // 16.1.2024
}

Пример 5: Расширение в Flutter context (практический)

extension BuildContextExtensions on BuildContext {
  // Удобный доступ к media query
  bool get isMobile => MediaQuery.of(this).size.width < 600;
  
  bool get isTablet => 
    MediaQuery.of(this).size.width >= 600 && 
    MediaQuery.of(this).size.width < 1200;
  
  bool get isDesktop => MediaQuery.of(this).size.width >= 1200;
  
  // Быстрые навигации
  void push<T>(Widget page) {
    Navigator.of(this).push(
      MaterialPageRoute(builder: (_) => page),
    );
  }
  
  void pushReplace<T>(Widget page) {
    Navigator.of(this).pushReplacement(
      MaterialPageRoute(builder: (_) => page),
    );
  }
  
  void pop<T>([T? result]) {
    Navigator.of(this).pop(result);
  }
  
  // Показать Snackbar
  void showSnackBar(String message, {Color? backgroundColor}) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: backgroundColor,
      ),
    );
  }
  
  // Быстрый доступ к theme
  ThemeData get theme => Theme.of(this);
  
  TextTheme get textTheme => theme.textTheme;
  
  ColorScheme get colorScheme => theme.colorScheme;
}

// Использование
class MyScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          if (context.isMobile)
            Text('Мобильное устройство')
          else if (context.isTablet)
            Text('Планшет')
          else
            Text('Десктоп'),
          
          ElevatedButton(
            onPressed: () {
              context.showSnackBar('Привет!');
              context.push(SecondScreen());
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

Правила и ограничения

Можно добавлять:

  • Методы (instance и static)
  • Getters и setters
  • Operators (только в некоторых случаях)

Нельзя добавлять:

  • Поля (properties)
  • Конструкторы
  • Модификаторы доступа (private/public)

Именование расширений

// Хорошо - описательные имена
extension StringValidation on String { }
extension ListUtilities on List { }
extension DateTimeFormatting on DateTime { }

// Плохо - неинформативные имена
extension Ext1 on String { }
extension S on String { }

Конфликты имен

Если несколько расширений определяют одинаковый метод:

extension StringFormatExt1 on String {
  String format() => toUpperCase();
}

extension StringFormatExt2 on String {
  String format() => toLowerCase();
}

void main() {
  final text = "Hello";
  
  // Указать явно, какое расширение использовать
  print(StringFormatExt1(text).format()); // HELLO
  print(StringFormatExt2(text).format()); // hello
}

Где хранить расширения

Структура проекта:

lib/
├── extensions/
│   ├── string_extensions.dart
│   ├── int_extensions.dart
│   ├── list_extensions.dart
│   ├── datetime_extensions.dart
│   └── context_extensions.dart
├── utils/
└── main.dart

main.dart:

import 'extensions/string_extensions.dart';
import 'extensions/int_extensions.dart';
import 'extensions/list_extensions.dart';
import 'extensions/datetime_extensions.dart';
import 'extensions/context_extensions.dart';

Преимущества ELT

  • Читаемость - код читается как естественный язык
  • Переиспользование - один раз написал, везде используешь
  • Группировка логики - вся логика работы с типом в одном месте
  • Не меняет исходный класс - no side effects
  • Discoverability - IDE автодополнение работает

Вывод

ELT (Extension Methods) в Dart — это мощный инструмент для расширения функциональности типов. Правильно использованные расширения:
  • Делают код более читаемым
  • Уменьшают дублирование
  • Упрощают работу с встроенными типами
  • Превращают утилиты в методы объектов

Главное — не переусложнять и называть расширения осмысленно.