Комментарии (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 — это мощный инструмент для расширения функциональности типов. Правильно использованные расширения:
- Делают код более читаемым
- Уменьшают дублирование
- Упрощают работу с встроенными типами
- Превращают утилиты в методы объектов
Главное — не переусложнять и называть расширения осмысленно.