(六)Dart Classes(类)、Constructors(构造函数)、Typedefs(别名)

一、Object(对象)

  • Dart 是一个面向对象编程语言。
  • Dart支持基于 mixin 的继承机制(相当于多继承,但不是多继承,Dart语言是单继承,mixin只是实现类似多继承的一种方式)。
  • 每个对象都是一个类的实例,所有的类都继承于 Object.。
  • 所有的类有同一个基类:Object
  • 使用 new 关键字和构造函数来创建新的对象。 构造函数名字可以为 ClassName 或者 ClassName.identifier。例如:
var jsonData = JSON.decode('{"x":1, "y":2}');

// Create a Point using Point().
var p1 = new Point(2, 2);

// Create a Point using Point.fromJson().
var p2 = new Point.fromJson(jsonData);
  • 使用点.来引用对象的变量或者方法:
var p = new Point(2, 2);

// Set the value of the instance variable y.
p.y = 3;

// Get the value of y.
assert(p.y == 3);

// Invoke distanceTo() on p.
num distance = p.distanceTo(new Point(4, 4));
  • 使用 ?. 来替代 . 可以避免当左边对象为 null 时候 抛出异常(有点类似swift语言)
// If p is non-null, set its y value to 4.
p?.y = 4;
  • 有些类提供了常量构造函数。使用常量构造函数 可以创建编译时常量,要使用常量构造函数只需要用 const 替代 new 即可
var p = const ImmutablePoint(2, 2);
  • 两个一样的编译时常量其实是 同一个对象
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);

assert(identical(a, b)); // They are the same instance!
  • 可以使用 Object 的 runtimeType 属性来判断实例 的类型,该属性 返回一个 Type 对象。
    var a = 'test';
    var b = 5;
    var c = [56,3,4,6,0];
    var d = {
      'key1':'value1',
      'key2':'value2'
    };

    // The type of a is String
    print('The type of a is ${a.runtimeType}');
    // The type of a is int
    print('The type of a is ${b.runtimeType}');
    // The type of a is List
    print('The type of a is ${c.runtimeType}');
    // The type of a is _InternalLinkedHashMap
    print('The type of a is ${d.runtimeType}');

二、Instance variables(实例化变量)

  • 在类定义中,所有没有初始化的变量都会被初始化为null
class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

main() {

/* 每个实例变量都会自动生成一个 getter 方法(隐含的)。
 Non-final 实例变量还会自动生成一个 setter 方法。
*/
  var point = new Point();
  point.x = 4;          // Use the setter method for x.
  assert(point.x == 4); // Use the getter method for x.
  assert(point.y == null); // Values default to null.
}

三、Constructors(构造函数)

注意:Constructors aren’t inherited(构造函数不会继承)
子类不会继承超类的构造函数。 子类如果没有定义构造函数,则只有一个默认构造函数 (没有名字没有参数)。如果你希望子类也有超类一样的命名构造函数, 你必须在子类中自己实现该构造函数。

3.1.Default constructors(默认构造函数)

如果你没有定义构造函数,则会有个默认构造函数。 默认构造函数没有参数,并且会调用超类的没有参数的构造函数。

  • 声明一个和类名相同的函数,来作为类的构造函数。
class Point {
  num x;
  num y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}
  • this 关键字指当前的实例。

注意: 只有当名字冲突的时候才使用 this。否则的话, Dart 代码风格样式推荐忽略 this

class Point {
  num x;
  num y;

//  Point(num x, num y) {
//    // There's a better way to do this, stay tuned.
//    this.x = x;
//    this.y = y;
//  }
  Point(num param1, num param2) {
    // 实例变量和参数名不冲突市,推荐忽略 this
    x = param1;
    y = param2;
  }

}
  • 把构造函数参数赋值给实例变量的场景太常见了, Dart 提供了一个语法糖来简化这个操作:
class Point {
  num x;
  num y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

3.2.Named constructors(命名构造函数)

使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数来更清晰的表明你的意图:

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map json) {
    x = json['x'];
    y = json['y'];
  }
}

3.3.Invoking a non-default superclass constructor(调用超类构造函数)

默认情况下,子类的构造函数会自动调用超类的 无名无参数的默认构造函数。 超类的构造函数在子类构造函数体开始执行的位置调用。 如果提供了一个 initializer list(初始化参数列表) ,则初始化参数列表在超类构造函数执行之前执行。 下面是构造函数执行顺序:

  1. initializer list(初始化参数列表)
  2. superclass’s no-arg constructor(超类的无名构造函数)
  3. main class’s no-arg constructor(主类的无名构造函数)

如果超类没有无名无参数构造函数, 则你需要手工的调用超类的其他构造函数。 在构造函数参数后使用冒号 (:) 可以调用 超类构造函数。

下面的示例中,Employee 类的构造函数调用 了超类 Person 的命名构造函数。

class Person {
  String firstName;

  Person.fromJson(Map data) {
    print('in Person');
  }
}

class Employee extends Person {
  // Person 没有默认构造函数
  // 必须调用 super.fromJson(data).
  Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
  }
}

main() {
  var emp = new Employee.fromJson({});

  // Prints:
  // in Person
  // in Employee
  if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
}
  • 由于超类构造函数的参数在构造函数执行之前执行,所以 参数可以是一个表达式或者 一个方法调用:
class Employee extends Person {
  // ...
  Employee() : super.fromJson(findDefaultData());
}

注意: 1. 如果在构造函数的初始化列表中使用 super(),需要把它放到最后。 2.调用超类构造函数的参数无法访问 this。 例如,参数可以为静态函数但是不能是实例函数。

  • 在构造函数体执行之前除了可以调用超类构造函数之外,还可以 初始化实例参数。 使用逗号分隔初始化表达式。

如Initializer list(初始化列表):

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // 构造函数体执行之前,初始化实例参数
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}

警告: 初始化表达式等号右边的部分不能访问 this

3.4.Redirecting constructors(重定向构造函数)

有时候一个构造函数会调动类中的其他构造函数。 一个重定向构造函数是没有代码的,在构造函数声明后,使用 :(冒号)调用其他构造函数。

class Point {
  num x;
  num y;

  // Point类的主构造函数
  Point(this.x, this.y);

  // 重定向构造函数,指向主构造函数,函数体为空
  Point.alongXAxis(num x) : this(x, 0);
}

3.5.Constant constructors(常量构造函数)

如果你的类提供一个状态不变的对象,你可以把这些对象 定义为编译时常量。要实现这个功能,需要定义一个 const 构造函数, 并且声明所有类的变量为 final

class ImmutablePoint {
  final num x;
  final num y;
  const ImmutablePoint(this.x, this.y);
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);
}

3.6.Factory constructors(工厂方法构造函数)

如果一个构造函数并不总是返回一个新的对象,则使用 factory 来定义 这个构造函数。例如,一个工厂构造函数 可能从缓存中获取一个实例并返回,或者 返回一个子类型的实例。

下面代码演示工厂构造函数 如何从缓存中返回对象。

class Logger {
  final String name;
  bool mute = false;

  // _cache 是一个私有库,幸好名字前有个 _ 。
  static final Map _cache =
      {};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

注意: 工厂构造函数无法访问 this

  • 使用 new 关键字来调用工厂构造函数。
var logger = new Logger('UI');
logger.log('Button clicked');

四、Callable classes(可调用的类)

如果 Dart 类实现了 call() 函数则 可以当做方法来调用。

在下面的示例中,WannabeFunction 类定义了一个 call() 方法,该方法有三个字符串参数,并且返回三个字符串 串联起来的结果。

class WannabeFunction {
  call(String a, String b, String c) => '$a $b $c!';
}

main() {
  var wf = new WannabeFunction();
  var out = wf("Hi","there,","gang");
  print('$out');
}

关于把类当做方法使用的更多信息,请参考 Emulating Functions in Dart(在 Dart 中模拟方法)。

五、Typedefs(别名)

在 Dart 语言中,方法也是对象。 使用 typedef, 或者 function-type alias 来为方法类型命名, 然后可以使用命名的方法。 当把方法类型赋值给一个变量的时候,typedef 保留类型信息。

下面的代码没有使用 typedef

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

 // 初始化,终止实现(不实现).
 int sort(Object a, Object b) => 0;

main() {
  SortedCollection coll = new SortedCollection(sort);

  // 我们只知道 compare 是一个 Function 类型,
  // 但是不知道具体是何种 Function 类型?
  assert(coll.compare is Function);
}

当把 f 赋值给 compare 的时候, 类型信息丢失了。 f 的类型是 (Object, Object) → int (这里 → 代表返回值类型), 当然该类型是一个 Function。如果我们使用显式的名字并保留类型信息, 开发者和工具可以使用 这些信息:

typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;

main() {
  SortedCollection coll = new SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

注意: 目前,typedefs 只能使用在 function 类型上,但是将来 可能会有变化。

由于 typedefs 只是别名,他们还提供了一种 判断任意 function 的类型的方法。例如:

typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
  assert(sort is Compare); // True!
}

你可能感兴趣的:((六)Dart Classes(类)、Constructors(构造函数)、Typedefs(别名))