一架梯子,一头程序猿,仰望星空!

Dart 类与接口


Dart是面相对象编程语言,对象都是由类创建的,所有类都是由Object类派生出来的子类,除了Object, 所有类只有一个父类(即只能继承一个父类)。

尽管Dart语言中一个类只能继承一个父类,但是Dart语言提供了mixins机制,可以复用多个类,达到类似多继承的效果。

1.类的定义

Dart语言类定义跟Java非常相似,下面看一个例子.

class Person {
  // 定义类成员属性,默认类的成员属性和方法都是共有的,类似java的public
  String name;
  // 以下划线 ( _ ) 开头命名的属性代表私有(private)成员属性,类方法也是类似的规则。
  int _age;

  // 跟类名同名的方法,为构造方法
  // 这里自定义了一个携带参数的构造方法。
  // 如果我们没有自定义构造方法,会自动生成一个不带参数的默认构造方法
  Person(String name, int age) {
    // 因为参数名和类属性名同名,可以使用this引用当前对象
  	this.name = name;
  	
  	// 可以忽略this关键字,直接引用类成员
  	_age = age;
  }

  // 定一个public的方法
  String greet(String who) => 'Hello, $who. I am $name, my age is $_age !';
}

通过上面的例子,我们基本知道Dart类定义语法结构,跟java非常类似,关于类方法(函数)的定义跟普通函数差不多,区别就是类方法定义在类内部,而且可以引用类的成员(属性和方法)。

关于函数的定义的细节,可以参考函数章节。

2.实例化变量

下面是类的基本用法。

// 实例化一个Person类的对象,这里使用的是我们前面自定义的构造方法初始化一个对象
var p = Person("tizi", 20);

// 通过点 (.) 调用对象的实例, 这里调用对象的方法
var ret = p.greet("dacui");

// 这是错误的,因为_age是私有属性,不能访问。
// var age = p._age;

Dart语言为我们提供了一个语法特性 ?. (问号点),用来访问对象的成员属性或者方法,他跟 点( . ) 的区别是它会判断对象是否为null ,如果对象为null就不执行属性访问操作。

// 例子
// 定义一个null对象
var p;
// 通过 ?. 访问对象的属性, 这里是不会报错的,tmp变量最终得到的是null。
// 因为p是null, 所以不会真的访问name成员。
var tmp = p?.name;

这个语法特性,主要目的是避免调用null变量的成员,而引发错误。

3.构造方法

如果我们没有自定义一个构造方法,会自动生成一个不带参数的默认构造方法。

// 这个类会生成默认的构造方法
class Person {
    String name;
}

// 通过默认构造方法实例化对象
var p = Person();

3.1. 自定义构造方法

class Point {
  num x, y;

  Point(num x, num y) {
    // 通过this访问成员属性,当然一般除非出现命名冲突,否则可以忽略this
    this.x = x;
    this.y = y;
  }
}

对于构造方法中,简单的赋值操作,Dart语言提供了更简洁的语法。

class Point {
  num x, y;

  // 直接将构造方法的第一个参数赋值给this.x, 第二个参数赋值给this.y
  Point(this.x, this.y);
}

3.2.初始化参数列表

Dart语言还为构造方法提供了 参数初始化列表 的语法,用于初始化对象参数。

class Point {
  num x, y;
  
  // 冒号 : 后面的表达式就是参数初始化列表,每个表达式用逗号分隔。
  Point(int x, int y) : this.x = x, this.y = y {
    // 使用参数初始化列表初始化对象属性,这里如果没有别的初始化工作要做,可以是空的。
  }
}

3.3.命名构造方法

在Dart语言中可以使用命名构造方法语法,创建多个构造方法。

命名构造方法语法格式: 类名.构造方法名(参数列表)

// 定义类
class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名构造方法origin
  Point.origin() {
    x = 0;
    y = 0;
  }
}

// 使用命名构造方法实例化对象
var p = Point.origin();

上面的例子也可以改写为:

class Point {
  num x, y;

  Point(this.x, this.y);
  
  // 命名构造方法origin
  // 这里通过 参数初始化列表,直接通过this调用上面的构造方法,传入两个参数0,初始化对象。
  Point.origin() : this(0, 0);
}

3.4.factory 构造方法

Dart语言还提供了一个特殊的构造方法,类似设计模式中的工厂模式,用来创建对象。

提示:factory 构造方法只能访问静态属性和静态成员方法,因此不能访问this引用。

下面看个factory构造方法的例子。

// 定义个日志类
class Logger {
  final String name;
  bool mute = false;

  // 定义一个私有的_cache属性,用来保存创建好的Logger对象
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  // 注意这个构造方法,前面使用了factory关键字修饰,这代表这个构造方法是一个工厂构造方法
  // 工厂构造方法不会每次都创建一个新的Logger对象
  factory Logger(String name) {
    // 根据name判断缓存的Logger对象是否存在
    if (_cache.containsKey(name)) {
      // 返回缓存的Logger对象
      return _cache[name];
    } else {
      // 如果没有缓存,则调用命名构造方法_internal创建一个Logger对象
      final logger = Logger._internal(name);
      // 根据name缓存logger
      _cache[name] = logger;
      // 返回新的Logger对象
      return logger;
    }
  }

  // 注意这个是一个私有的命名构造方法。
  Logger._internal(this.name);

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

使用factory工厂构造方法

// 根据UI关键词,返回一个Logger对象,每次根据同样的参数都可以返回同一个对象
var logger = Logger('UI');
logger.log('Button clicked');

4.继承一个类

通过extends关键字继承一个类。

// 定一个父类
class Television {
  void turnOn() {
    print("i am father");
  }
}

// 定义一个子类, 继承Television父类
class SmartTelevision extends Television {
  // 重写父类的方法
  void turnOn() {
    print("i am son");
  }
}

使用子类

var s = SmartTelevision();
s.turnOn();

输出

i am son

继承一个类,将拥有父类的所有公有属性和方法,如果出现相同的方法,则子类的方法优先被调用,当然子类也可以通过super访问父类成员。

class SmartTelevision extends Television {
  void turnOn() {
    // 通过super访问父类成员
    super.turnOn();
  }
}

5.抽象类和抽象方法

抽象类就是不能实例化的类,一般都是用来定义接口,抽象方法就是没有实现的方法。

例子:

// 使用abstract关键词修饰的类,就是抽象类
abstract class Doer {
  // 抽象类跟普通类一样,可以定义成员变量,成员方法。

  // 定义个抽象方法,这个方法我们没有实现具体的功能。
  void doSomething(); 
}

// 继承抽象类Doer
class EffectiveDoer extends Doer {
  // 实现抽象类的抽象方法
  void doSomething() {
    print("实现了抽象方法doSomething");
  }
}

继承抽象类,子类必须要实现所有抽象方法,否则会报错。

6.接口

在Dart语言中接口的定义通常是由抽象类实现的,上面已经介绍过了,这里介绍Dart语言的隐式接口,默认情况每一个类都隐含一个包含所有公有成员的接口定义。

如果我们想实现一个类A的接口,而不想继承A,可以使用implements关键词实现类A的接口。

例子:

// 定义一个类
class Person {
  // 包含在隐式接口里面
  String name;

  // 构造方法不包含在隐式接口里面
  Person(this.name);

  // 包含在隐式接口里面
  String greet(String who) => 'Hello, $who. I am $name.';
}

// 实现 Person 的接口,而不继承他
class Impostor implements Person {
  // 接口属性也需要实现
  String name;
  // 实现接口的方法
  String greet(String who) => 'Hi $who. Do you know who I am?';
}

// 定义测试函数,用来调用greet方法
String greetBob(Person person) => person.greet('Bob');

void main() {
  // 实例化Person, 打印结果
  print(greetBob(Person('Tizi')));
  // 实例化Impostor, 打印结果
  print(greetBob(Impostor()));
}

输出

Hello, Bob. I am Tizi.
Hi Bob. Do you know who I am?

Dart语言允许实现多个接口。

// 例子,实现多个类的接口,使用逗号分隔多个类。
class Point implements Comparable, Location {...}