美文网首页
Dart(2.2) - 类(Classes)

Dart(2.2) - 类(Classes)

作者: Longshihua | 来源:发表于2019-04-30 11:06 被阅读0次

类(Classes)

Dart 是一种面向对象语言,包含类和基于mixin的继承两部分。每个对象是一个类的实例,并且 Object 是所有类的父类。基于mixin的继承指的是每个类(除了Object)都只有一个父类,类体还可以在多个类继承中被重用。

使用类成员(Using class members)

对象的成员包含函数(functions)和数据(data)。当你调用一个方法(method)的时候,方法能够访问对象的函数和数据

使用点(.)语法引用实例变量和方法

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));

使用 ?.来替代.可以避免当左边对象为null时候抛出异常:

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

使用构造器(Using constructors)

你可以使用构造器创建对象,构造器的名字可以是类名(ClassName),或者类名.标识符(ClassName.identifier)形式。例如:下面的代码使用了Point()Point.fromJson()构造器来创建Point对象

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中成为可选

一些类提供常量构造函数(constant constructors),要创建一个编译时用的常量构造函数,使用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!

const上下文中,你能够在构造器或者字面量之前省略const,比如:创建一个常量的map

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

我们可以只留第一个const关键字即可

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

如果常量构造器不再常量上下文(constant context)范围内并且没有使用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!

注意:在Dart2const关键字在常量上下文中是可选的

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

为了在运行时获取对象的类型,可以使用runtimeType属性,它返回 Type对象

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

每个实例变量都会自动生成一个getter方法(隐含的)。Non-final实例变量还会自动生成一个setter方法。详情参考Getters and setters

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)

定义一个和类名字一样的方法就定义了一个构造函数,还可以带有其他可选的标识符,详情参考 Named 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;
  }
}

this 关键字指当前的实例。

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

由于把构造函数参数赋值给实例变量的场景太常见了, Dart提供了一个语法糖来简化这个操作:

class Point {
  num x;
  num 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)

使用命名构造函数可以为一个类声明多个构造函数,或者说是提供额外的声明:

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(初始化参数列表) ,则初始化参数列表在超类构造函数执行之前执行。 下面是构造函数执行顺序:

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

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

class Person {
  String firstName;

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

class Employee extends Person {
  // Person does not have a default constructor;
  // you must call 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(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。

重定向构造函数(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);
}

常量构造器并不是经常创建常量,更多细节看using constructors

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

如果一个构造函数并不总是返回一个新的对象,则使用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。 例如下面示例中的distanceTo()函数就是实例函数:

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

是一种提供对方法属性读和写的特殊方法。每个实例变量都有一个隐式的getter方法,合适的话可能还会有 setter 方法。你可以通过实现 getterssetters 来创建附加属性,也就是直接使用 getset 关键词:

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);
}

借助于gettersetter ,你可以直接使用实例变量,并且在不改变客户代码的情况下把他们包装成方法。

注:不论是否显式地定义了一个 getter,类似增量(++)的操作符,都能以预期的方式工作。为了避免产生任何向着不期望的方向的影响,操作符一旦调用 getter ,就会把他的值存在临时变量里。

抽象方法(Abstract methods)

Instancegettersetter 方法可以是抽象的,也就是定义一个接口,但是把实现交给其他的类。抽象方法只能存在于抽象类(abstract classes)

要创建一个抽象方法,使用分号(;)代替方法体:

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.
}

下面的类不是抽象的,但是定义了一个抽象函数,这样 的类是可以被实例化的:

class SpecializedContainer extends AbstractContainer {
  // ...Define more constructors, fields, methods...

  void updateChildren() {
    // ...Implement updateChildren()...
  }

  // Abstract method causes a warning but
  // doesn't prevent instantiation.
  void doSomething();
}

隐式接口(Implicit interfaces)

每个类隐式的定义了一个接口,含有类的所有实例和它实现的所有接口。如果你想创建一个支持类BAPI 的类A,但又不想继承类B ,那么,类A应该实现类B的接口。

一个类实现一个或更多接口通过用implements子句声明,然后提供API接口要求。例如:

// 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创建一个子类,同时supper将指向父类:

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

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

重写方法(Overriding members)

子类可以覆写实例函数,gettersetter。使用@override标记方法是被重写的

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

重写操作符(Overridable operators)

下表中的操作符可以被覆写。 例如,如果你定义了一个Vector类, 你可以定义一个+函数来实现两个向量相加

屏幕快照 2019-04-30 上午10.42.20.png

下面是重写了 + 和 - 操作符的示例:

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的getter函数。 关于重写 ==hashCode 的示例请参考Implementing map keys

关于覆写的更多信息请参考 Extending a class.

noSuchMethod()方法

下面是重写Object类的 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
  • 接收者在未实现的方法中有静态类型,而且接收者的dynamic类型已经实现了noSuchMethod()方法

更多信息可以看noSuchMethod forwarding specification.

枚举类型(Enumerated types)

枚举类型通常称之为 enumerations 或者 enums, 是一种特殊的类,用来表现一个固定数目的常量。

使用枚举(Using enums)

使用 enum 关键字来定义枚举类型:

enum Color { red, green, blue }

枚举类型中的每个值都有一个indexgetter 函数, 该函数返回该值在枚举类型定义中的位置(从 0 开始)。 例如,第一个枚举值的位置为 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 statements 中使用枚举。如果你没有处理所有该枚举类型的值的话,则会抛出一个警告:

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'
}

枚举类型具有如下的限制:

  • 无法继承枚举类型、无法使用mix in、无法实现一个枚举类型
  • 无法显示的初始化一个枚举类型

详情请参考Dart language specification

为类添加新特征:mixins(Adding features to a class: mixins)

Mixins是一种在多类继承中重用一个类代码的方法。要使用 mixins,在with关键字后面跟一个或多个mixin的名字 下面是示例显示了如何使用 mixin:

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

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

要实现mixin ,就创建一个继承Object类的子类,不声明任何构造函数,不调用 super 。除非你想mixin 不能被重用而是一个正常类,使用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指定必须的父类

mixin MusicalPerformer on Musician {
  // ···
}

类变量和函数(Class variables and methods)

使用 static 关键字来实现类级别的变量和函数。

静态变量(Static variables)

静态变量(类变量)对于类状态和常数是有用的:

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

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

只有当静态变量被使用时才被初始化。

注:本章内容依据代码风格指南推荐的lowerCamelCase 来为常量命名。

静态方法(Static methods)

静态方法(类方法)不在一个实例上进行操作,因而不必访问 this 。例如:

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);
}

注:考虑到使用高阶层的方法而不是静态方法,是为了常用或者广泛使用的工具和功能。

你可以将静态方法作为编译时常量。例如,你可以把静态方法作为一个参数传递给静态构造函数。

参考

Dart

相关文章

  • Dart(2.2) - 类(Classes)

    类(Classes) Dart 是一种面向对象语言,包含类和基于mixin的继承两部分。每个对象是一个类的实例,并...

  • (六)Dart Classes(类)、Constructors(

    一、Object(对象) Dart 是一个面向对象编程语言。 Dart支持基于 mixin 的继承机制(多继承)。...

  • 类 (Classes)

    Dart是一种面向对象的语言,具有类和基于mixin的继承。每个对象都是一个类的实例,所有类都是从Object继承...

  • Classes - 类

    来源于 Ry’s Objective-C Tutorial - RyPress 一个学习Objective-C基础...

  • case class

    Case Classes Scala 支持 case classes 记法。Case Class 就是普通的类, ...

  • Sonar代码审查工具简析

    1.代码规模 Classes(classes),类数目。 Comment lines(comment_lines)...

  • JavaScript 类(classes)

    ECMAScript 2015 中引入的 JavaScript 类(classes) 实质上是 JavaScrip...

  • c++ 对象基础(一)

    Ref cppreference - inlinecppreference - classes 类 类包含数据(变...

  • Flutter/Dart - Dart中的抽象类 多态 和接口

    Dart中的抽象类 Dart中的抽象类: Dart抽象类主要用于定义标准,子类可以继承抽象类,也可以实现抽象类接口...

  • 第二十一章 Classes

    第二十一章 Classes Classes 类定义并不是 ObjectScript 的正式组成部分。相反,可以在类...

网友评论

      本文标题:Dart(2.2) - 类(Classes)

      本文链接:https://www.haomeiwen.com/subject/wraenqtx.html