目录
扩展方法是什么
Dart 2.7版本新增了扩展方法的功能
旨在不修改类、不继承类的情况下,向一个类添加新的函数。扩展使我们可以合理地遵循开闭原则(对扩展开放,而对修改是封闭的)。
比如:当使用其他人编写的API或者开源库的时候,我们很难去改变这些API,但是又想添加我们自己想要的功能,这个时候我们可以考虑使用extension来扩展API。
扩展方法如何使用
声明:
extension <extension name> on <type> {
(<member definition>)*
}
- extension:扩展函数使用关键字 。
- extension name:是扩展函数的名称。
- on :关键字后接需要扩展的类型。
- 大括号:内部是扩展的方法。
使用1: 比如,我们希望将一个整数字符串转换为 int 类型整数,但是 String 类型并没有 toInt 方法,这时扩展函数就有了用武之地,我们给 String 扩展一个 toInt 方法。
extension StringExtension on String {
int toInt() {
return int.parse(this);
}
}
调用时可以这么写:
'10'.toInt();
当然,不使用扩展函数也可以实现,在不使用扩展的情况我们会这样写:
int.parse('10');
但是这种写法没有扩展后的写法美观、使用方便。
使用2: 给定一个时间,需要判断该时间是否是今天。
extension DateTimeExtension on DateTime {
bool isSameDay(DateTime date) {
final dateFormat = DateFormat("yyyy-MM-dd");
final date1 = dateFormat.format(this);
final date2 = dateFormat.format(date);
return date1 == date2;
}
}
调用:
DateTime.now().isSameDay(DateTime.parse("2022-07-01 18:09"));
使用3: 对Widget类进行扩展,比如: 我们可以扩展写ui时用的比较多的设置边距的padding方法及点击事件。
extension WidgetExt on Widget {
Widget padding(EdgeInsetsGeometry padding) {
return Padding(
child: this,
padding: padding,
);
}
Material gesture({
GestureTapCallback? onTap, //点击
GestureTapCallback? onDoubleTap, //双击
GestureLongPressCallback? onLongPress, //长按
}) {
return InkWell(
child: this,
onTap: onTap,
onDoubleTap: onDoubleTap,
onLongPress: onLongPress,
);
}
}
给一个文本设置边距及点击事件可以这么写
Center(
child: Text(
'You have pushed the button this many times:$_counter',
).padding(EdgeInsets.all(10)).gesture(onTap: _incrementCounter),
)
再来和实现同样功能不使用扩展的写法对比一下
Center(
child: InkWell(
onTap: _incrementCounter,
child: Padding(
padding: EdgeInsets.all(10),
child: Text(
'You have pushed the button this many times:$_counter',
),
),
),
)
可以看出使用扩展可以简化代码,减少嵌套。
当然,扩展不仅可以定义方法,还可以定义其他成员,例如getter,setter和operator。
使用时的注意事项
- 拓展方法可用于推断类型
因为变量a通过推断可以得到a的类型是String,只要该类型可以被推断出来,那么就可以使用extention扩展。var a = '10'; print(a.toInt()); //Output: 10
-
拓展方法不能用于dynamic
dynamic a = '10'; print(a.toInt());
运行时会报错
======== Exception caught by widgets library ======================================================= The following NoSuchMethodError was thrown building MyHomePage(dirty, state: _MyHomePageState#de9bf): Class 'String' has no instance method 'toInt'. Receiver: "10" Tried calling: toInt()
这是因为扩展方法针对静态类型进行了解析. 声明dynamic,无法在静态解析时确定其真正的类型。
-
API冲突
假设现在有2个扩展函数,分别在 string_ext1.dart 和 string_ext2.dart 文件中
string_ext1.dart代码://string_ext1.dart extension StringExt on String { int toInt() { return int.parse(this); } }
string_ext2.dart代码:
//string_ext2.dart extension StringExt2 on String { int toInt() { return int.parse(this); } double toDouble() { return double.parse(this); } }
如果在同一个文件里引入了这两个2个扩展函数并使用 toInt 方法:
//test.dart import 'package:flutter_log/common/string_ext1.dart'; import 'package:flutter_log/common/string_ext2.dart'; print('10'.toInt());
此时编译器会报错
这是因为所引入的2个扩展函数中都有 toInt 方法。
解决方案1: 使用 hide 和 show 来限制
//test.dart import 'package:flutter_log/common/string_ext1.dart'; import 'package:flutter_log/common/string_ext2.dart' hide StringExt2; print('10'.toInt());
这种方式会导致StringExt2中的其他方法无法使用
解决方案2: 显式指定使用哪一个扩展函数
import 'package:flutter_log/common/string_ext1.dart'; import 'package:flutter_log/common/string_ext2.dart'; print(StringExt('10').toInt()); print(StringExt2('10').toInt());
这适合扩展名不一样的情况,如果两个扩展名都相同,比如string_ext1.dart和string_ext3.dart中都有StringExt
//string_ext3.dart extension StringExt on String { int toInt() { return int.parse(this); } double toDouble() { return double.parse(this); } }
那么编译器会报错
解决方案3: 用as 给扩展文件指定别名
import 'package:flutter_log/common/string_ext1.dart'; import 'package:flutter_log/common/string_ext3.dart' as parse; print(StringExt('10').toInt()); print(parse.StringExt('10').toInt()); //在导入的文件中,如果函数是唯一的,那么不需要“xxx”去调用“StringExt”扩展类,直接调用扩展函数即可。 print('10'.toDouble());
小结:两个文件名称不同,但是扩展名一样,都是“StringExt”,而且有相同的扩展函数“toInt”,那么可以使用别名“as xxx”,然后用“xxx”去调用“StringExt”扩展类,再去调用里面的扩展函数。但是在导入的文件中,如果函数是唯一的,那么不需要“xxx”去调用“StringExt”扩展类,直接调用扩展函数即可。上例中的'10'.toDouble()这个“toDouble”就是“string_ext3.dart”里面的唯一的函数, 所以不加别名调用也是可以的。
-
不能重写
不能定义与要扩展的类中同名的函数,比如下面这个toString方法,否则编译器会报错。也就是说扩展只能新增方法,但不能对已有的方法进行修改或重写,所以更好地遵循了开闭原则。
总结
首先,先简单说一下扩展与继承的区别,因为我们在对一个类进行扩展的时候一般都会用继承的方式来实现,而dart新增的这个extension方法与继承的区别是怎样的呢?
- 继承会生成一个子类型,可以实例化对象,而扩展只是在当前类型基础上扩展其方法。
- 继承的父类要是有非默认的构造函数,那么必须显示调用父类构造函数,扩展没有限制。
- 一般继承我们关心的是怎么利用父类已有方法进行重写,扩展则更关心实现新的方法。
最后小结:
- 扩展可以在不修改类 、 不继承类的情况下,向一个类添加新函数,更符合开闭原则。
- 扩展是在某一个类型上追加一些方法,而且这个方法还只和这个类型有关,相较于传统的工具方法的调用方式更简单直接,表意性更强。
- 使用extension可以简化代码,减少嵌套,代码阅读起来更为清晰。
- 通过extension我们可以封装一些常用方法,从而提高开发效率。