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.calltear-off 可作为类型明确的回调传递。- 简单局部逻辑使用闭包通常更轻量,复杂策略才需要可调用对象。