Dart中的扩展方法

本文介绍了Dart 2.7中新增的扩展方法,如何不修改类结构为API添加新功能,以及使用时的注意事项,包括类型推断、dynamic限制、API冲突解决策略和扩展原则的遵循。通过实例演示了如何简化代码和提升开发效率。
摘要由CSDN通过智能技术生成

目录

扩展方法是什么

扩展方法如何使用

使用时的注意事项

总结


扩展方法是什么

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。
 

使用时的注意事项


  1. 拓展方法可用于推断类型
    var a = '10';
    print(a.toInt()); //Output: 10
    
    因为变量a通过推断可以得到a的类型是String,只要该类型可以被推断出来,那么就可以使用extention扩展。
  2. 拓展方法不能用于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,无法在静态解析时确定其真正的类型。

  3. 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”里面的唯一的函数, 所以不加别名调用也是可以的。

  4. 不能重写
    不能定义与要扩展的类中同名的函数,比如下面这个toString方法,否则编译器会报错。

    也就是说扩展只能新增方法,但不能对已有的方法进行修改或重写,所以更好地遵循了开闭原则。

总结


首先,先简单说一下扩展与继承的区别,因为我们在对一个类进行扩展的时候一般都会用继承的方式来实现,而dart新增的这个extension方法与继承的区别是怎样的呢?

  1. 继承会生成一个子类型,可以实例化对象,而扩展只是在当前类型基础上扩展其方法。
  2. 继承的父类要是有非默认的构造函数,那么必须显示调用父类构造函数,扩展没有限制。
  3. 一般继承我们关心的是怎么利用父类已有方法进行重写,扩展则更关心实现新的方法。

最后小结:

  • 扩展可以在不修改类 、 不继承类的情况下,向一个类添加新函数,更符合开闭原则。
  • 扩展是在某一个类型上追加一些方法,而且这个方法还只和这个类型有关,相较于传统的工具方法的调用方式更简单直接,表意性更强。
  • 使用extension可以简化代码,减少嵌套,代码阅读起来更为清晰。
  • 通过extension我们可以封装一些常用方法,从而提高开发效率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值