7.Classes-类(Dart中文文档)

Dart是一个面向对象的语言,同时增加了混入(mixin)继承的特性。对象都是由类初始化生成的,所有的类都由Object对象继承。混入继承意味着尽管所有类(除了Object类)只有一个父类,但是类的代码体可以在多个类中重复使用。(个人理解:mixin,extends,implements,extends是类似java单继承,implements类似java的多接口实现,但这些都是运行时使用,mixin应该是在编译时使用,类似于jsp的include,比如 A with B,其实是在编译时,将两个类的代码合并,编译成classes) 。

Using class members 类成员

对象由成员变量和方法构成。你可以用点(.)调用方法或者成员变量

var p = 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(Point(4, 4));
Use ?. instead of . to avoid an exception when the leftmost operand is null:

// If p is non-null, set its y value to 4.
p?.y = 4;

Using constructors 构造方法

你可以用构造方法创建对象,构造方法是ClassName或者ClassName.identifier.下面是构造方法的两种写法:

Point() and Point.fromJson() constructors:

var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});

下面的构造方法多了new关键字:

var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});

版本注意:new 关键字在Dart2中是可选的

一些类提供了静态构造方法。当你需要这些创建编译常量对象,可以在构造对象代码前加上const关键字。

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!

在编译常量上下中,可以忽略后续的const关键字,也可以达到同样效果

// Lots of const keywords here.
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};

这是简化的写法:

// Only one const, which establishes the constant context.
const pointAndLine = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果用常量构造器初始化对象,但是没有加入const关键字,它将是非常量对象

var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant

assert(!identical(a, b)); // NOT the same instance!

版本注意: const关键字的忽略写法在Dart2中才有效

Getting an object’s type 获取对象类型

在运行时获取对象的类型,你可以用Object的对象的 runtimeType属性。

print('The type of a is ${a.runtimeType}');

Instance variables 成员变量

下面是如何定义成员变量:

class Point {
  num x; // Declare instance variable x, initially null.
  num y; // Declare y, initially null.
  num z = 0; // Declare z, initially 0.
}

所有为初始化变量的值是null.

所有成员变量都内置get方法。非final的成员变量内置了set方法。

class Point {
  num x;
  num y;
}

void main() {
  var point = 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 构造器

类构造器是和类名同名的函数,(其中还有一种命名构造函数的写法,后续介绍)。

class Point {
  num x, y;

  Point(num x, num y) {
    // There's a better way to do this, stay tuned.
    this.x = x;
    this.y = y;
  }
}

上面的例子中,thisg关键字可以读取成员变量。
注意:使用this只有在方法参数和成员变量有冲突时才用,其它场景可以不写

如果构造器的入参和成员变量对应,可以直接赋值,可以用如下的简化写法:

class Point {
  num x, y;

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

Default constructors 默认构造器

如果没有定义构造器,类将提供一个无参构造器,如果类有一个父类,也会调用父类的无参构造器

Constructors aren’t inherited 构造器不可继承

子类不会继承父类的构造器,如果子类没有定义构造器,它只有默认的无参构造器,不会去继承父类的构造器。

Named constructors 命名构造器

Dart可以使用命名构造器去实现多个构造逻辑。

class Point {
  num x, y;

  Point(this.x, this.y);

  // Named constructor
  Point.origin() {
    x = 0;
    y = 0;
  }
}

构造器是不可继承的,这个规则在命名构造器同样有效。如果你想要一个和父类一样的命名构造器,需要在子类的自己定义同样的构造器。

Invoking a non-default superclass constructor 调用父类的非默认构造器

默认情况下,子类的构造器将调用父类的默认构造器。父类构造器先于子类构造器执行,如果子类构造器有初始化执行器列表,它将先于父类构造器执行,如下是执行顺序:

initializer list
superclass’s no-arg constructor
main class’s no-arg constructor

如果父类没有默认构造器,你必须手动调用父类的其它构造器。写法是在子类构造器名后面加上冒号(😃,写上父类构造器调用。
Because the arguments to the superclass constructor are evaluated before invoking the constructor, an argument can be an expression such as a function call:

class Employee extends Person {
  Employee() : super.fromJson(getDefaultData());
  // ···
}

注意:父类构造器的参数不接收this, 只能接受静态方法。

Initializer list 初始化执行器

在构造器后面除了可以追加父类构造器外,还可以追加初始化执行器。

// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, num> json)
    : x = json['x'],
      y = json['y'] {
  print('In Point.fromJson(): ($x, $y)');
}

注意:右边的初始化执行器不接受this写法。

在开发模式下,你可以通过assert在初始化执行器中判断参数是否为空。

Point.withAssert(this.x, this.y) : assert(x >= 0) {
  print('In Point.withAssert(): ($x, $y)');
}

Redirecting constructors 重定向构造器

有些时候,定义的构造器只为了传递给另一个构造器,那么构造器的代码体是空的,同时后面直接追加:目标构在器

class Point {
  num x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(num x) : this(x, 0);
}

Constant constructors 常量构造器

如果对象在创建后,不再改变,你可以将它写成编译常量。这些样类,首先构造器要有const关键字,同时成员变量得是final.

class ImmutablePoint {
  static final ImmutablePoint origin =
      const ImmutablePoint(0, 0);

  final num x, y;

  const ImmutablePoint(this.x, this.y);
}

Factory constructors 工厂构造器

当构造器使用factory关键字时,对象创建不一定每次都是一个新对象。factory可能从缓存中提取对象返回或者创建新对象。

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

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

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

  Logger._internal(this.name);

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

注意:工厂对象不能使用this关键字

调用工厂构造器和其它构造器的写法没差别。

var logger = Logger('UI');
logger.log('Button clicked');

Methods 方法

Instance methods 成员方法

成员方法可以使用成员变量和this关键字,

import 'dart:math';

class Point {
  num x, y;

  Point(this.x, this.y);

  num distanceTo(Point other) {
    var dx = x - other.x;
    var dy = y - other.y;
    return sqrt(dx * dx + dy * dy);
  }
}

Getters and setters get和set

get和set是读写对象成语变量的特殊方法。通过.调用成员变量,就是调用内置的get方法,set方法也是一样的。

你也可以通过get或者set关键字重载get和set方法

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

Note: Operators such as increment (++) work in the expected way, whether or not a getter is explicitly defined. To avoid any unexpected side effects, the operator calls the getter exactly once, saving its value in a temporary variable.

Abstract methods 抽象方法

成员方法,get方法,set方法都是定义为在抽象类中定义成抽象方法,注意是在抽象类中,非抽象类不可定义。

抽象方法直接在方法后加上;号,不用实现代码。

abstract class Doer {
  // Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // Provide an implementation, so the method is not abstract here...
  }
}

Abstract classes 抽象类

你可以用abstract关键字来定义抽象类,注意,抽象类不可初始化。抽象类适合做接口定义,具体实现通过继承抽象类。抽象类的实现初始化方法,一般用工厂构造器。

// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
  // Define constructors, fields, methods...

  void updateChildren(); // Abstract method.
}

Implicit interfaces 接口实现

如果A继承B,则A将具备B的所有成员变量和方法,也同时具备B继承的所有接口方法。但是,你如果不像继承B类,但有希望拥有B类的方法,可以继承B的相应接口。

// A person. The implicit interface contains greet().
class Person {
  // In the interface, but visible only in this library.
  final _name;

  // Not in the interface, since this is a constructor.
  Person(this._name);

  // In the interface.
  String greet(String who) => 'Hello, $who. I am $_name.';
}

// An implementation of the Person interface.
class Impostor implements Person {
  get _name => '';

  String greet(String who) => 'Hi $who. Do you know who I am?';
}

String greetBob(Person person) => person.greet('Bob');

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor()));
}

下面是多接口继承例子,

class Point implements Comparable, Location {...}

Extending a class 扩展类

通过extends继承父类,可以用super去访问父类。

class Television {
  void turnOn() {
    _illuminateDisplay();
    _activateIrSensor();
  }
  // ···
}

class SmartTelevision extends Television {
  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    _initializeMemory();
    _upgradeApps();
  }
  // ···
}

Overriding members 重载

子类可以重载成员方法,get方法,set 方法,你可以用@override是标记该方法被重载

class SmartTelevision extends Television {
  @override
  void turnOn() {...}
  // ···
}

To narrow the type of a method parameter or instance variable in code that is type safe, you can use the covariant keyword.

Overridable operators 操作符重载

你可以重载如下操作符:

< + | []

/ ^ []=
<= ~/ & ~
= * << ==
– % >>
‘注意:你会注意到!=是不用于重载的,因为它和是等效的,比如(e1!=e2)和!(e1e2)这两个是相同效果'

下面是+,-的重载示例:

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // Operator == and hashCode not shown. For details, see note below.
  // ···
}

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

注意:如果你重载了==,也需要重载hashCode方法。

noSuchMethod()

如果对象尝试调用不存在的变量或者方法,会抛出noSuchMethod异常,这个异常可以通过重载该方法,增加自定义逻辑。

class A {
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
}

你一般不能调用未继承的方法除非出现如下情况:

它是一个dynamic对象,因为是动态类型,所以编译器不会检查。

它继承了一个抽象类,同时它没有做抽象方法做具体实现,但是定义了 noSuchMethod()函数,这样,编译器也不会检查。

更多的内容请查看noSuchMethod

Enumerated types 枚举类型

枚举类型一般用于表示一些限定范围内的常量。

Using enums

可以用enum关键字定义枚举。

enum Color { red, green, blue }

每个枚举对象的属性都会赋予一个整数值,它是从0开始,基于属性位置递增。例如,Color的枚举对象,第一个是0,第二个是1

assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);

你可以使用values获取枚举属性的值数组。

List<Color> colors = Color.values;
assert(colors[2] == Color.blue);

你可以用枚举来做switch语句,编译器会在你没有处理所有枚举属性时,作出提示。

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

枚举类型有下面的限制:
枚举不可继承,混入,也不可以被继承
枚举类不可初始化

Adding features to a class: mixins 混入

Mixins是一种在多个类中重用代码的方式。

混入的写法是:with关键字加上多个混入的类

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

用于mixin的对象是一个继承Object的无构造函数的类,如果你想将用于混入的类和普通类区分开来,可以用 mixin关键字替代class.

mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

mixin可以通过on关键字来限定哪些类的子类才可以用它做用户(不太理解:To specify that only certain types can use the mixin — for example, so your mixin can invoke a method that it doesn’t define — use on to specify the required superclass:)

mixin MusicalPerformer on Musician {
  // ···
}

注意:mixin关键字在dart2.1采用到,早期版本用abstract class.

Class variables and methods 类成员变量和方法

使用static 关键字修饰类的成员变量和方法就形成了静态变量和静态方法。

Static variables 静态变量

Static variables (class variables) are useful for class-wide state and constants:

class Queue {
  static const initialCapacity = 16;
  // ···
}

void main() {
  assert(Queue.initialCapacity == 16);
}

静态变量在它使用时才初始化。

Static methods 静态方法

静态方法可以通过类直接调用:

import 'dart:math';

class Point {
  num x, y;
  Point(this.x, this.y);

  static num distanceBetween(Point a, Point b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}

void main() {
  var a = Point(2, 2);
  var b = Point(4, 4);
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}

第八篇翻译 ,Generics 泛型

posted @ 2019-01-02 23:25  lowezheng  阅读(662)  评论(0编辑  收藏  举报