Menu
Coddy logo textTech

Переопределение noSuchMethod

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

Когда вы вызываете метод, который не существует у объекта, Dart обычно выбрасывает NoSuchMethodError. Однако вы можете переопределить специальный метод noSuchMethod(), чтобы перехватывать эти вызовы и обрабатывать их корректно.

Чтобы использовать noSuchMethod(), ваш класс также должен реализовывать интерфейс или использовать dynamic типизацию.

Метод получает объект Invocation, содержащий сведения о попытке вызова:

class Flexible {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    var methodName = invocation.memberName.toString();
    print('Called: $methodName');
    return null;
  }
}

void main() {
  dynamic obj = Flexible();
  obj.anyMethod();      // Вызвано: Symbol("anyMethod")
  obj.anotherOne(42);   // Вызвано: Symbol("anotherOne")
}

Объект Invocation предоставляет полезную информацию о вызове — memberName возвращает имя метода в виде Symbol, positionalArguments содержит переданные аргументы, а isMethod сообщает, был ли это вызов метода, а не геттера или сеттера.

class Logger {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    print('Method: ${invocation.memberName}');
    print('Args: ${invocation.positionalArguments}');
    return 'handled';
  }
}

void main() {
  dynamic log = Logger();
  var result = log.save('data', 123);
  print(result);  // handled
}

Эта техника полезна для создания прокси-объектов, мок-классов для тестирования или создания гибких API, которые реагируют на произвольные вызовы методов. Однако используйте её с осторожностью, так как она обходит проверку типов во время компиляции.

challenge icon

Задание

Легко

Давайте создадим логгер методов, который перехватывает и записывает любые вызовы методов, сделанные к объекту! Вы создадите класс, использующий noSuchMethod() для захвата информации о вызовах неопределенных методов и интеллектуального реагирования на основе имени метода.

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

  • method_logger.dart: Создайте класс MethodLogger, который перехватывает вызовы неопределенных методов. Ваш логгер должен переопределять noSuchMethod(), чтобы:
    • Извлечь имя метода из вызова (это будет Symbol, например Symbol("methodName") — вам нужно будет преобразовать его в строку и извлечь только имя)
    • Вывести имя метода и любые позиционные аргументы в следующем формате: Called: [methodName] with args: [arguments]
    • Вернуть специальный ответ в зависимости от имени метода:
      • Если имя метода содержит get, вернуть строку 'data retrieved'
      • Если имя метода содержит save, вернуть строку 'data saved'
      • В противном случае вернуть строку 'unknown action'
  • main.dart: Импортируйте ваш логгер и продемонстрируйте, как он обрабатывает различные вызовы неопределенных методов. Не забудьте использовать динамическую типизацию (dynamic), чтобы Dart позволял вызывать методы, которых не существует:
    • Создайте экземпляр MethodLogger, используя динамическую переменную
    • Вызовите у него getUserData('Alice', 42) и выведите возвращенный результат
    • Вызовите у него saveRecord('important') и выведите возвращенный результат
    • Вызовите у него processItem() (без аргументов) и выведите возвращенный результат

Чтобы извлечь имя метода из Symbol, вы можете преобразовать его в строку, а затем извлечь имя, находящееся между кавычками. Например, Symbol("myMethod").toString() даст вам 'Symbol("myMethod")'.

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

Called: getUserData with args: [Alice, 42]
data retrieved
Called: saveRecord with args: [important]
data saved
Called: processItem with args: []
unknown action

Шпаргалка

Метод noSuchMethod() позволяет перехватывать вызовы методов, которые не существуют у объекта. Чтобы использовать его, ваш класс должен использовать типизацию dynamic или реализовывать интерфейс.

Переопределите noSuchMethod() для обработки вызовов неопределенных методов:

class Flexible {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    var methodName = invocation.memberName.toString();
    print('Called: $methodName');
    return null;
  }
}

void main() {
  dynamic obj = Flexible();
  obj.anyMethod();      // Called: Symbol("anyMethod")
  obj.anotherOne(42);   // Called: Symbol("anotherOne")
}

Объект Invocation предоставляет информацию о вызове:

  • memberName — имя метода в виде Symbol
  • positionalArguments — аргументы, переданные в метод
  • isMethod — указывает, был ли это вызов метода, а не геттера или сеттера
class Logger {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    print('Method: ${invocation.memberName}');
    print('Args: ${invocation.positionalArguments}');
    return 'handled';
  }
}

void main() {
  dynamic log = Logger();
  var result = log.save('data', 123);
  print(result);  // handled
}

Эта техника полезна для создания прокси-объектов, мок-классов для тестирования или гибких API, но ее следует использовать с осторожностью, так как она обходит проверку типов во время компиляции.

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

import 'method_logger.dart';

void main() {
  // TODO: Создайте экземпляр MethodLogger, используя динамическую типизацию
  // Использование dynamic позволяет вызывать методы, которых не существует во время компиляции
  dynamic logger;
  
  // TODO: Вызовите getUserData('Alice', 42) и выведите результат
  
  // TODO: Вызовите saveRecord('important') и выведите результат
  
  // TODO: Вызовите processItem() без аргументов и выведите результат
  
}
quiz iconПроверьте себя

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

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