跳到主要内容

Callable objects(可调用对象)

类只要声明名为 call 的实例方法,其对象就可以使用函数调用语法。可调用对象把函数式调用体验与对象的状态、构造参数和其他成员结合起来,适合可配置的转换器、验证器和策略对象。

声明 call 方法

class Greeter {
final String greeting;

Greeter(this.greeting);

String call(String name) => '$greeting, $name!';
}

void main() {
final greet = Greeter('Hello');

print(greet('Dart')); // Hello, Dart!
print(greet.call('Dart')); // Hello, Dart!
}

greet('Dart')greet.call('Dart') 的简写。Greeter 仍是普通类,可以拥有字段、构造函数和其他方法。

保存状态

与普通顶层函数相比,可调用对象可以在多次调用之间保存状态:

class CallCounter {
int _count = 0;

int call() {
_count++;
return _count;
}

int get count => _count;
}

void main() {
final next = CallCounter();

print(next()); // 1
print(next()); // 2
print(next.count); // 2
}

有状态对象可能使执行结果依赖调用顺序。只需捕获少量局部值时,闭包通常更简单;需要清晰的配置、状态或辅助 API 时,可调用对象更合适。

接收配置与依赖

构造函数可以把固定配置注入可调用对象:

class RangeValidator {
final int min;
final int max;

const RangeValidator({required this.min, required this.max});

bool call(int value) => value >= min && value <= max;
}

void main() {
const isPercentage = RangeValidator(min: 0, max: 100);

print(isPercentage(80)); // true
print(isPercentage(120)); // false
}

对象名称可以表达策略含义,构造参数则固定每次调用共享的规则。

可选参数与泛型 call

call 是普通实例方法,因此支持位置参数、命名参数、可选参数、异步返回值和类型参数:

class Converter {
T call<T>(Object value, T Function(Object) convert) {
return convert(value);
}
}

class Formatter {
String call(String value, {bool uppercase = false}) {
return uppercase ? value.toUpperCase() : value;
}
}

void main() {
final converter = Converter();
final formatter = Formatter();

print(converter<int>('42', (value) => int.parse(value as String)));
print(formatter('dart', uppercase: true)); // DART
}

Dart 不支持按不同参数签名重载多个 call 方法。需要多种调用形态时,应使用可选参数、泛型,或定义名称明确的其他方法。

作为回调使用

需要明确的函数值时,可以取得 call 方法的 tear-off:

class Normalizer {
String call(String value) => value.trim().toLowerCase();
}

void main() {
final normalizer = Normalizer();
final values = [' Dart ', ' FLUTTER '];

final normalized = values.map(normalizer.call).toList();
print(normalized); // [dart, flutter]
}

normalizer.call 的类型是 String Function(String),可以直接传给要求该函数签名的 API。显式 tear-off 也能让读者清楚这里传递的是回调,而不是立即执行对象。

可调用对象与闭包

String Function(String) makeGreeter(String greeting) {
return (name) => '$greeting, $name!';
}

闭包同样能够捕获配置。选择时可以参考:

  • 一次性、局部且逻辑很短:优先闭包。
  • 需要具名类型、构造校验、多个公开成员或可测试状态:考虑可调用对象。
  • 只需普通无状态操作:顶层函数或静态函数通常最直接。

TypeScript 对比:JavaScript 函数本身可以携带属性,但普通类实例不能仅靠定义 call() 就使用 instance() 语法。Dart 对 call 方法提供了专门的调用语法,可调用对象仍保持普通对象模型。

常见误区

  • 方法名必须是 call;其他名称不会让对象获得函数调用语法。
  • 可调用对象不会因此变成函数声明,它仍然是类的实例。
  • 多个不同签名的 call 不能重载,应改用可选参数或其他具名方法。
  • 有状态 call 会让结果依赖历史,文档和测试应明确这种行为。

小结

  • 声明 call 实例方法后,对象可以写成 object(arguments)
  • call 支持普通方法具备的参数、泛型和异步能力。
  • 可调用对象适合封装固定配置、依赖和跨调用状态。
  • object.call tear-off 可作为类型明确的回调传递。
  • 简单局部逻辑使用闭包通常更轻量,复杂策略才需要可调用对象。