Menu
CoddyTech

重写 noSuchMethod

本课属于 Coddy Dart 学习旅程的“面向对象编程”单元。第 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();      // Called: Symbol("anyMethod")
  obj.anotherOne(42);   // Called: Symbol("anotherOne")
}

Invocation 对象提供了有关调用的有用信息:memberName 会以 Symbol 的形式提供方法名,positionalArguments 包含传入的参数,而 isMethod 会告诉你这次调用是方法调用,还是 getter 或 setter 调用。

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);  // 已处理
}

这种技巧适用于创建代理对象、用于测试的模拟类,或构建能够响应任意方法调用的灵活 API。不过要谨慎使用,因为它会绕过编译时类型检查。

challenge icon

挑战

简单

让我们构建一个方法记录器,用来拦截并记录对象上的任意方法调用!你将创建一个类,使用 noSuchMethod() 捕获未定义方法调用的信息,并根据方法名做出恰当响应。

你将把代码分成两个文件:

  • method_logger.dart:创建一个 MethodLogger 类,用来拦截对未定义方法的调用。你的记录器应重写 noSuchMethod(),以便:
    • 从调用中提取方法名(它会是类似 Symbol("methodName") 的 Symbol,你需要将其转换为字符串,并只提取名称)
    • 按以下格式打印方法名和所有位置参数: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

亲手试试

import 'method_logger.dart';

void main() {
  // TODO: 使用动态类型创建一个 MethodLogger 实例
  // 使用 dynamic 可以调用编译时不存在的方法
  dynamic logger;
  
  // TODO: 调用 getUserData('Alice', 42) 并打印结果
  
  // TODO: 调用 saveRecord('important') 并打印结果
  
  // TODO: 不传参数调用 processItem(),并打印结果
  
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Dart 在线编译器