Переопределение 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, которые реагируют на произвольные вызовы методов. Однако используйте её с осторожностью, так как она обходит проверку типов во время компиляции.
Задание
ЛегкоДавайте создадим логгер методов, который перехватывает и записывает любые вызовы методов, сделанные к объекту! Вы создадите класс, использующий noSuchMethod() для захвата информации о вызовах неопределенных методов и интеллектуального реагирования на основе имени метода.
Вы организуете свой код в два файла:
method_logger.dart: Создайте классMethodLogger, который перехватывает вызовы неопределенных методов. Ваш логгер должен переопределятьnoSuchMethod(), чтобы:- Извлечь имя метода из вызова (это будет Symbol, например
Symbol("methodName")— вам нужно будет преобразовать его в строку и извлечь только имя) - Вывести имя метода и любые позиционные аргументы в следующем формате:
Called: [methodName] with args: [arguments] - Вернуть специальный ответ в зависимости от имени метода:
- Если имя метода содержит
get, вернуть строку'data retrieved' - Если имя метода содержит
save, вернуть строку'data saved' - В противном случае вернуть строку
'unknown action'
- Если имя метода содержит
- Извлечь имя метода из вызова (это будет Symbol, например
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— имя метода в виде SymbolpositionalArguments— аргументы, переданные в метод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() без аргументов и выведите результат
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий