Ответ
Extension-методы (расширения) в Dart — это мощный механизм, позволяющий добавлять новую функциональность к существующим классам, даже к тем, исходный код которых вы не можете изменить (например, классы из стандартной библиотеки или внешних пакетов). Это делается без создания подкласса и без использования утилитарных классов.
Базовый синтаксис:
extension MyStringExtensions on String {
// Добавляем метод capitalize() ко всем строкам
String capitalize() {
if (isEmpty) return this;
return '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
}
// Добавляем геттер
bool get isPalindrome => reversed == this;
}
void main() {
print('dart'.capitalize()); // Вывод: Dart
print('racecar'.isPalindrome); // Вывод: true
}
Практическое применение во Flutter-разработке:
-
Упрощение работы с контекстом:
extension ContextExtensions on BuildContext { // Быстрое получение размеров экрана Size get screenSize => MediaQuery.of(this).size; double get screenWidth => screenSize.width; double get screenHeight => screenSize.height; // Упрощённый вызов навигации void popToRoot() => Navigator.of(this).popUntil((route) => route.isFirst); } // Использование в виджете @override Widget build(BuildContext context) { if (context.screenWidth > 600) { return _buildTabletLayout(); } return _buildMobileLayout(); } -
Расширение встроенных типов для предметной области:
extension DateTimeExtensions on DateTime { // Форматирование даты для отображения в UI String toDisplayFormat() { return DateFormat('dd.MM.yyyy HH:mm').format(this); } // Проверка, является ли дата сегодняшним днём bool get isToday { final now = DateTime.now(); return year == now.year && month == now.month && day == now.day; } } // Использование Text(DateTime.now().toDisplayFormat()); -
Добавление вспомогательных методов к коллекциям:
extension ListExtensions<T> on List<T> { // Безопасное получение элемента или null T? elementAtOrNull(int index) => (index >= 0 && index < length) ? this[index] : null; // Разделение списка на подсписки фиксированного размера (чанки) List<List<T>> chunk(int size) { return [ for (var i = 0; i < length; i += size) sublist(i, i + size > length ? length : i + size), ]; } }
Важные ограничения и особенности:
- Расширения не могут добавлять поля (переменные экземпляра) в класс, только методы и геттеры/сеттеры.
- Они работают на этапе компиляции и не влияют на производительность во время выполнения.
- Имя расширения (например,
MyStringExtensions) используется для разрешения конфликтов, если к одному классу применяются несколько расширений с одинаковыми именами методов. - Расширения должны быть явно импортированы в файл, где вы хотите их использовать (
import 'extensions/string_extensions.dart';).
Ответ 18+ 🔞
Слушай, вот тебе про Dart-расширения, но так, чтобы понятно было, а не как в этих ихних документациях, где от скуки сдохнуть можно. Представь, что ты купил машину, а потом тебе говорят: «А давай мы к ней прикрутим ништяки, которых с завода нет?» Вот extension-методы — это и есть такие ништяки для классов. Хочешь к стандартной строке или к какому-нибудь виджету BuildContext прилепить свою функцию? Без проблем, ёпта! Не надо городить подклассы или отдельные утилитки — просто навешиваешь, как ксенон на дедову десятку.
Как это выглядит, блядь:
extension MyStringExtensions on String {
// Делаем так, чтобы любая строка могла сама себя с большой буквы написать
String capitalize() {
if (isEmpty) return this; // На пустую не зачем тратиться
return '${this[0].toUpperCase()}${substring(1).toLowerCase()}';
}
// Или вот геттер, который проверяет, читается ли строка одинаково с обеих сторон
bool get isPalindrome => reversed == this;
}
void main() {
print('dart'.capitalize()); // Выведет: Dart — красота же!
print('racecar'.isPalindrome); // Выведет: true — а это вообще палиндром, ядрёна вошь!
}
А теперь, где это реально вкатывает во Flutter, чтобы жизнь мёдом не казалась:
-
Контекст — твой лучший друг и главная боль. Сколько раз ты писал
MediaQuery.of(context).size.width? Рука отваливается, честно. Давай сделаем красиво:extension ContextExtensions on BuildContext { // Быстро хватаем размеры экрана Size get screenSize => MediaQuery.of(this).size; double get screenWidth => screenSize.width; double get screenHeight => screenSize.height; // Или вот — нахуй все экраны к чёртовой матери, вернуться в самое начало void popToRoot() => Navigator.of(this).popUntil((route) => route.isFirst); } // Используем в виджете без этой всей ебалы с вложенностью @override Widget build(BuildContext context) { // Смотри как лаконично, аж терпения ебать ноль, чтобы по-старому писать if (context.screenWidth > 600) { return _buildTabletLayout(); } return _buildMobileLayout(); } -
Даты и время. Ну что за мудёж каждый раз с
DateFormat? Сделаем один раз и забудем, как страшный сон.extension DateTimeExtensions on DateTime { // Форматируем для показа человеку, а не машине String toDisplayFormat() { return DateFormat('dd.MM.yyyy HH:mm').format(this); } // Проверка, сегодня ли это число? Чтобы не сравнивать вручную год, месяц и день bool get isToday { final now = DateTime.now(); return year == now.year && month == now.month && day == now.day; } } // Использование — одна строка, а не три Text(DateTime.now().toDisplayFormat()); // "27.05.2024 15:30" — сразу видно -
Списки (List). Стандартный
Dartиногда скудноват. Хочешь безопасно получить элемент или разбить список на куски?extension ListExtensions<T> on List<T> { // Берём элемент, а если индекс кривой — возвращаем null, а не Exception в рожу T? elementAtOrNull(int index) => (index >= 0 && index < length) ? this[index] : null; // Делим большой список на мелкие порции (чанки). Овердохуища полезно для пагинации или сеток. List<List<T>> chunk(int size) { return [ for (var i = 0; i < length; i += size) sublist(i, i + size > length ? length : i + size), ]; } }
Но, чувак, есть важные подводные булыжники, не обдери ноги:
- Поля добавить нельзя. Только методы, геттеры и сеттеры. Нельзя воткнуть в
Stringновую переменнуюmySuperField. Это как пытаться приварить пятую дверь к седану — технически возможно, но всем будет понятно, что ты ебанько. - Всё решается при компиляции. На скорости выполнения это не сказывается, можно не бздеть.
- Имя расширения — это твоё клеймо. Если у двух расширений для
Stringбудет методsuperMethod(), то придётся указывать, какое именно ты используешь, или импортировать с префиксом. Конфликты, блядь, как в коммуналке. - Надо явно импортировать. Не сработает по волшебству. Подключил файл с расширениями — пользуйся. Не подключил — сиди и пиши всё руками, распиздяй.
Короче, инструмент — огонь. Позволяет писать код чище, быстрее и без лишнего бойлерплейта. Главное — не увлекаться и не начать расширять int методом toPizza(), а то потом сам от себя охуеешь при ревью.