【Dart】泛型的使用以及模块组织

122 阅读5分钟

前言

之前我们介绍了 Dart 中的基本语法和面向对象(OOP)的内容,今天我们将介绍 Dart 中泛型(Generics)以及库的内容。

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情

泛型

泛型的用法同 Java 和 TypeScript 中的用法类似,这里只着重说明 dart 中的特性。

使用集合字面量

List 和 map 字面量可以被参数化。参数化字面量和已经认识的所有字面量一样,仅仅是在字面量的开始括号之前添加<type>(对于 List 类型来说)或者添加<keyType, valueType>(对于 map 类型来说)。

 var names = <String>['Seth', 'Kathy', 'Lars'];
 var pages = <String, String>{
   'index.html': 'Homepage',
   'robots.txt': 'Hints for web robots',
   'humans.txt': 'We are people, not machines'
 };

构造函数的参数化类型

要在使用构造函数时指定一个或多个类型,请将类型放在类名后面的尖括号<…>中。

 var names = List<String>();
 names.addAll(['Seth', 'Kathy', 'Lars']);
 var nameSet = Set<String>.from(names);

下面的代码创建了一个具有整数键和视图类型值的map映射:

 var views = Map<int, View>();

泛型集合及其包含的类型

dart 通用类型被具体化,这意味着它们在运行时携带它们的类型信息。例如,可以测试集合的类型:

 var names = List<String>();
 names.addAll(['Seth', 'Kathy', 'Lars']);
 print(names is List<String>); // true

注意: 相反,Java 中的泛型使用擦除,这意味着泛型类型参数在运行时被删除。在 Java 中,可以测试一个对象是否是一个列表,但不能测试它是否是 String 类型的列表。

限制参数化类型

在实现泛型类型时,可能希望限制其参数的类型,这个时候需要使用 extends 关键字

 class Foo<T extends SomeBaseClass> {
   // Implementation goes here...
   String toString() => "Instance of 'Foo<$T>'";
 }
 
 class Extender extends SomeBaseClass {...}

可以使用 SomeBaseClass 或它的任何子类作为泛型参数:

 var someBaseClassFoo = Foo<SomeBaseClass>();
 var extenderFoo = Foo<Extender>();

当不知道泛型参数时默认会是 extends 的类本身(否则会是 dynamic):

 var foo = Foo();
 print(foo); // Instance of 'Foo<SomeBaseClass>'

传入其他的非 extends 的类或其子类时会报错:

 var foo = Foo<Object>(); // Error

泛型方法

最初,dart 仅仅在类中支持泛型。后来一种称为泛型方法的新语法允许在方法和函数中使用类型参数。

 T first<T>(List<T> ts) {
   // Do some initial work or error checking, then...
   T tmp = ts[0];
   // Do some additional checking or processing...
   return tmp;
 }

用法同 TypeScript 中非常相似,这里不过多介绍。

库和可见性

import 和 library 指令可以帮助创建模块化和可共享的代码库。库不仅提供api,而且包含隐私部分:以下划线(_)开头的标识符仅在库中可见。

可以使用包来分发库,一般使用用不到,在这里就不做多的讲解。

注: 每个 dart 应用程序都是一个库,即使它不使用库指令。

使用库

  • 使用import来指定如何在另一个库的范围中使用来自一个库的命名空间。

    例如,dart web应用程序通常使用dart:html库,它们可以这样导入:

     import 'dart:html';
    

    导入一个库仅仅需要提供库的 URI。对于内置库,URI具有特定的形式(dart:scheme)。对于其他库,可以使用文件路径或者包:scheme的形式。包:scheme形式指定包管理器(如 pub 工具(类似 npm))提供的库。例如:

     import 'package:test/test.dart';
    

    注意: URI 表示统一资源标识符。url(统一资源定位器)是一种常见的 URI。

  • 使用library定义这个库的名字,但库的名字并不影响导入,因为import语句用的是字符串Uri

     library person;
    

指定一个库前缀

如果导入两个具有冲突标识符的库,那么可以为一个或两个库指定一个前缀(命名空间)。

 import 'package:lib1/lib1.dart';
 import 'package:lib2/lib2.dart' as lib2;
 
 // Uses Element from lib1.
 Element element1 = Element();
 
 // Uses Element from lib2.
 lib2.Element element2 = lib2.Element();

库的可见性

如果只想使用库的一部分,可以有选择地导入库,这也是库的可见性。

有两种关键字来表示需要使用的内容:

  • show:导入的库只能使用 show 后面的变量。
  • hide:导入的库不能使用 hide 后面的变量。
 // Import only foo.
 import 'package:lib1/lib1.dart' show foo;
 
 // Import all names EXCEPT foo.
 import 'package:lib2/lib2.dart' hide foo;

懒加载库

延迟加载(也称为懒加载)允许应用程序在需要时按需加载库。

以下是一些可能使用延迟加载的情况:

  • 减少应用程序的初始启动时间。
  • 例如,要执行A/B测试——尝试算法的其他实现。
  • 加载很少使用的功能,如可选屏幕和对话框。

要延迟加载库,必须首先使用deferred as进行导入。

 import 'package:greetings/hello.dart' deferred as hello;

当需要库时,使用库的标识符调用loadLibrary()方法。

 Future greet() async {
   await hello.loadLibrary();
   hello.printGreeting();
 }

注; 可以在库上多次调用loadLibrary(),但该库只加载一次。

在使用延迟加载时,请记住以下几点:

  • 在导入文件中,递延库的常量不是常量。记住,这些常量在延迟库加载之前是不存在的
  • 不能在导入文件中使用来自延迟库的类型。相反,考虑将接口类型移动到由延迟库和导入文件导入的库。
  • dart 隐式地将loadLibrary()插入到定义使用deferred作为名称空间的名称空间中。函数的作用是:返回一个未来(也就是 JavaScript 中的 Promise)。

库的实现

实现库的适合可以依靠partpart of关键字:

  • 为了维护一个库,我们可以把各个功能放到各个dart文件中
  • part of所在文件不能包括import、library等关键字
  • 可以包含在part关键字所在文件中
  • 建议避免使用partpart of语句,因为那样会使代码很难阅读、修改
  • 可以多用librarypart加字符串类型的 Uri 类似 include,表示包含某个文件
  • part of加库名表示该文件属于那个库
 // math.dart文件开头
 library math;
 part 'point.dart';
 part 'random.dart';
 
 // point.dart文件开头
 part of math;
 
 // random.dart文件开头
 part of math;

有关如何实现库包的建议,请参阅创建库包,包括:

  • 如何组织库的代码。
  • 如何使用 export 指令。
  • 何时使用 part 指令。