Menu
Coddy logo textTech

Методы расширения

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 83 из 110.

Иногда вам нужно добавить функциональность в класс, которым вы не владеете — например, во встроенные типы Dart String или int. Вы не можете изменять их исходный код, а наследование не помогает в случае с финальными классами. Именно здесь на помощь приходят методы расширения.

Расширения позволяют добавлять новые методы к существующим типам без их изменения или создания подклассов:

extension StringExtras on String {
  String reverse() {
    return split('').reversed.join('');
  }
  
  bool get isPalindrome {
    var cleaned = toLowerCase().replaceAll(' ', '');
    return cleaned == cleaned.split('').reversed.join('');
  }
}

void main() {
  print('hello'.reverse());        // olleh
  print('radar'.isPalindrome);     // true
}

Синтаксис: extension ExtensionName on Type. Внутри вы определяете методы и геттеры точно так же, как в обычном классе. После определения эти методы появляются во всех экземплярах этого типа, как если бы они были встроенными.

Расширения работают с любым типом, включая ваши собственные классы и универсальные типы:

extension IntExtras on int {
  int squared() => this * this;
}

extension ListExtras<T> on List<T> {
  T? get secondOrNull => length >= 2 ? this[1] : null;
}

void main() {
  print(5.squared());                    // 25
  print([1, 2, 3].secondOrNull);         // 2
}

Обратите внимание, что внутри расширения this ссылается на экземпляр, для которого вызван метод. Расширения — это аккуратный способ улучшить существующие типы, сохраняя ваш код организованным и читаемым.

challenge icon

Задание

Легко

Давайте расширим встроенные типы Dart полезной функциональностью с помощью методов расширения! Вы создадите расширения, которые добавляют полезные методы к типам String и List, а затем используете их в своем основном файле.

Вы организуете свой код в два файла:

  • extensions.dart: Создайте два расширения, которые добавляют новые возможности к существующим типам:
    • Расширение под названием StringUtils для String с:
      • Методом countVowels(), который возвращает количество гласных (a, e, i, o, u — без учета регистра) в строке
      • Геттером initials, который возвращает первую букву каждого слова (разделенных пробелами), объединенных вместе и в верхнем регистре. Например, "hello world" превращается в "HW"
    • Расширение под названием ListUtils<T> для List<T> с:
      • Методом safeGet(int index), который возвращает элемент по индексу, если он существует, или null, если индекс находится вне диапазона
      • Геттером hasMultiple, который возвращает true, если в списке более одного элемента
  • main.dart: Импортируйте свои расширения и продемонстрируйте их в действии:
    • Создайте строку "Object Oriented Programming" и выведите количество гласных в ней в формате: Vowels: [count]
    • Выведите инициалы этой же строки в формате: Initials: [initials]
    • Создайте список целых чисел [10, 20, 30]
    • Выведите результат safeGet(1) в формате: Element at 1: [value]
    • Выведите результат safeGet(5) в формате: Element at 5: [value]
    • Выведите, содержит ли список несколько элементов, в формате: Has multiple: [true/false]

Помните, что внутри расширения this относится к экземпляру, для которого вызывается метод. Ваши расширения заставят эти типы выглядеть так, будто эти методы встроены в них изначально!

Ожидаемый вывод:

Vowels: 9
Initials: OOP
Element at 1: 20
Element at 5: null
Has multiple: true

Шпаргалка

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

Синтаксис для создания расширения — extension ExtensionName on Type:

extension StringExtras on String {
  String reverse() {
    return split('').reversed.join('');
  }
  
  bool get isPalindrome {
    var cleaned = toLowerCase().replaceAll(' ', '');
    return cleaned == cleaned.split('').reversed.join('');
  }
}

После определения методы расширения можно вызывать на экземплярах этого типа так, как если бы они были встроенными:

print('hello'.reverse());        // olleh
print('radar'.isPalindrome);     // true

Расширения работают с любым типом, включая встроенные типы, ваши собственные классы и универсальные типы:

extension IntExtras on int {
  int squared() => this * this;
}

extension ListExtras<T> on List<T> {
  T? get secondOrNull => length >= 2 ? this[1] : null;
}

print(5.squared());                    // 25
print([1, 2, 3].secondOrNull);         // 2

Внутри расширения this ссылается на экземпляр, для которого вызван метод.

Попробуйте сами

import 'extensions.dart';

void main() {
  // Создать строку
  String text = "Object Oriented Programming";

  // TODO: Вывести количество гласных, используя countVowels()
  // Формат: Vowels: [count]

  // TODO: Вывести инициалы, используя геттер initials
  // Формат: Initials: [initials]

  // Создать список целых чисел
  List<int> numbers = [10, 20, 30];

  // TODO: Вывести результат safeGet(1)
  // Формат: Element at 1: [value]

  // TODO: Вывести результат safeGet(5)
  // Формат: Element at 5: [value]

  // TODO: Вывести, содержит ли список несколько элементов
  // Формат: Has multiple: [true/false]
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование