注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

韩国恺的博客

hanguokai.com

 
 
 

日志

 
 

Dart 语言入门(三)——面向对象  

2012-11-19 14:47:28|  分类: Dart |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
更新日期:2013年3月17日

Dart 是基于类的、纯面向对象语言,类型是可选的。类是单继承的,但可以实现多个接口。Dart 中的所有东西都是对象,包括数字、函数等,它们都继承自 Object,并且对象的默认值都是 null(包括数字)。下一步 Dart 还计划实现 Mixin-based 的继承和 Mirror-based 的反射。


类和接口
Dart 中的类和 Java 类似,用 class 关键字定义一个类,用 extends 关键字继承一个类,用 implements 关键字实现一个或多个接口。但和 Java 不同的是,在 Dart 中类和接口是统一的,类就是接口。因为每个类都有一个隐式接口,包括类的所有(可见的)实例成员(字段和方法)和它所实现的所有接口,但类的构造函数不属于它的隐式接口。一个只有抽象方法的类就相当于传统意义上的接口(比如 Java 的接口)。所以,你可以继承一个类,也可以实现一个“类”。如果你想复用已有类的一些实现,那么你可以继承一个类;如果你只是想具有该类/接口的外在行为,那么你可以实现这个类。因此,你不必再考虑是使用接口还是抽象类的问题了,它们没有区别。

在语法上,如果一个方法没有实现,那么直接以分号结束即可,这就是一个抽象方法。抽象方法不需要用 abstract 关键字声明,有实现的方法就是非抽象方法,没有实现的就是抽象方法。这很自然。如果一个类中包含抽象方法,那么“应该”用 abstract 关键字声明这个类为抽象类,但并不强制要求,没有声明只会给出警告。抽象类表明它不应该被实例化,而是期望由其它类来实现,但这也不是强制的,不过调用一个未实现的方法在运行时肯定会报错。
abstract class Shape { // 定义了一个 Shape 类/接口
num perimeter(); // 这是一个抽象方法,不需要abstract关键字,是隐式接口的一部分。

}

class Rectangle implements Shape { // Rectangle 实现了 Shape 接口
final num height, width;
Rectangle(num this.height, num this.width); // 紧凑的构造函数语法
num perimeter() => 2*height + 2*width; // 实现了 Shape 接口要求的 perimeter 方法
}

class Square extends Rectangle { // Square 继承 Rectangle
Square(num size) : super(size, size); // 调用超类的构造函数
}

上面的 Rectangle 构造函数的参数中,使用了 this.field 的特殊语法,这实际上是一个语法糖,让你的代码更简练。它的效果等价于:

Rectangle(num height, num width) {

this.height = height;

this.width = width;

}

另外,如果构造函数非常简单、没有额外的代码实现,那么直接用分号结束即可。构造函数与方法不同,这并不说明构造函数是抽象的。构造函数也不属于隐式接口的一部分。


方法
和其它语言一样,Dart 使用点号(.)引用一个对象上的方法或字段。另外,Dart 还支持使用两个点号(..)进行级联调用(cascade operator),级联调用和普通方法调用一样,但是它并不返回方法的返回值,而是返回调用对象自身,这样你就可以连续调用同一个对象上的多个方法了。级联调用是语言层面的支持,不需要你特意设计 API 让方法返回自身。

query('#button') // 获得页面上的一个 button 对象
..text = 'Click to Confirm' // 设置 button 的显示文字
..classes.add('important') // 给 button 添加一个 CSS 样式
..onClick.listen((e) => window.alert('Confirmed!')); // 给 button 添加一个点击事件的处理函数

上面的代码就是一个级联调用的示例,在一个语句中连续调用同一个对象,而不用写多个语句。它等价于下面的代码:

var button = query('#button');
button.text = 'Click to Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));



字段和 getter/setter
在 Dart 中,字段和方法是统一的。每个字段隐式对应了一对 getter 和 setter 方法,访问字段就是调用 getter/setter 方法。getter/setter 方法是用 get 和 set 关键字定义的特殊方法,但只是以使用字段的形式调用(如 obj.x,而不是 obj.x() )。如果字段是 final 或 const 的,那么它只有一个 getter 方法,没有setter 方法。当你读取一个实例变量时(如 obj.x),实际上是调用了 x 对应的 getter 方法,设置该实例变量的值时(如 obj.x = 1)实际是调用了它的 setter 方法。你也可以利用 getter/setter 方法构造出伪字段,它并不是真是的字段,但使用起来二者无异。

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

num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;

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


main() {

var rect = new Rectangle(3, 4, 20, 15);
print(rect.left);
print(rect.bottom); // 获取 bottom
rect.top = 6;
rect.right = 12; // 设置 right

}

上面的 Rectangle 类有四个真正的字段:left, top, width 和 height。它还用两对 getter 和 setter 方法定义了两个额外的逻辑属性:right 和 bottom。对使用者而言,字段和 getter/setter 没有区别。所以你可以大胆的使用字段,哪天想要修改实现时,可以随时把字段改造成 getter/setter 方法,而不会影响现有的使用者。

另外,getter/setter 方法和普通方法一样,可以是抽象方法,可以被子类覆盖,但字段不行。

静态变量和静态方法
使用 static 关键字可以定义属于类的变量和方法。当然静态方法中不能使用 this. 调用实例方法,但实例方法可以调用静态方法。注意,静态方法和静态变量在类的外部应该使用类名调用,而不是使用实例对象调用。在类的内部,实例方法可以直接调用静态方法,也可以使用类名调用静态方法,但同样不能用 this. 调用静态方法。

class Point {
num x;
num y;

static Logger log; // 静态变量
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);
}
}

因为 Dart 支持顶层定义的函数和变量,所以你没必要把所有东西都防止类中,可以考虑放在库的顶层,例如 dart:math 库中就是以顶层函数定义的。


构造函数
要创建一个对象,需要使用 new 关键字加上类的构造函数。构造函数的名字可以是 ClassName 或者 ClassName.identifier 的形式,后者被称为命名构造函数。另外,Dart 还支持工厂构造函数,给你创建对象最大的灵活性。所有这些都统一使用 new constructor() 的形式,包括工厂构造函数。

构造函数的执行顺序:
  1. 创建对象
  2. 设置声明时初始化的实例变量的值
  3. 执行构造函数初始化列表,以及显示或隐式调用超类的构造函数
  4. 执行构造函数

默认构造函数
如果你没有提供任何构造函数,Dart 会自动提供一个无参数的默认构造函数,默认构造函数还会自动调用超类的默认构造函数。

命名构造函数
和大多数语言一样,构造函数的名字可以是类名,也就是用 new ClassName() 的形式创建对象。对于像 Java 这样的静态语言,可以定义多个同名(类名)的构造函数,因为可以通过不同类型的参数区分。但是动态语言大都不支持这样,使用类名的构造函数只能有一个,所以动态语言往往使用一个构造函数实现多个构造函数的功能,这不是很理想的组织和使用方式。另一种选择是使用常规的函数或静态方法创建对象,但这样就不能统一使用 new 的形式创建对象了。Dart 也是动态语言,为了解决这个问题,Dart 发明了命名构造函数,使用 new ClassName.identifier() 的形式创建对象。命名构造函数看起来像方法一样,只是使用的使用前面有 new 关键字。一个类可以有任意多个命名构造函数,但只能有一个标准构造函数。另外,命名构造函数和方法在相同的命名空间内,所以它们不能重名。

import 'dart:math';

class Point {
num x, y;
Point(this.x, this.y); // 指定 x 和 y 的标准构造函数
Point.zero() : x = 0, y = 0; // x 和 y 都为0的命名构造函数
Point.polar(num theta, num radius) { // 通过极坐标系统中的 θ 和 r 定义的构造函数
x = cos(theta) * radius;
y = sin(theta) * radius;
}
}

这里这个 Point 类有三个构造函数,一个标准构造函数和两个命名构造函数。你可以像下面这样使用:

import 'dart:math';

main() {
var a = new Point(1, 2);
var b = new Point.zero();
var c = new Point.polar(PI, 4.0);
}


调用超类构造函数
Dart 中的构造函数不会被继承。如果子类的构造函数没有显示地调用超类的构造函数,那么会自动调用超类的默认的无参数的构造函数。如果超类没有定义默认的无参数构造函数(比如定义了一个命名构造函数而没有定义标准构造函数,或者标准构造函数是有参数的),那么子类必需显示地调用超类的构造函数。

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

class Employee extends Person {
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}

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

// 输出:
// in Person
// in Employee
}



初始化列表
在构造函数执行之前,除了调用超类构造函数外,还可以初始化实例变量。例如:

class Point {
num x;
num y;

Point(this.x, this.y);

Point.fromJson(Map json) : x = json['x'], y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
}

注意,初始化列表不能使用 this. 。

重定向构造函数
如果一个构造函数仅仅是调用另一个构造函数,那么可以使用重定向构造函数。例如:

class Point {
num x;
num y;

Point(this.x, this.y); // 主构造函数
Point.alongXAxis(num x) : this(x, 0); // 重定向给另一个构造函数
}


常量构造函数
如果要创建一个不可变的对象,你可以把它定义为编译时常量的对象。要求定义一个 const 构造函数,并且所有的实例变量都是 final 或 const 的。

class ImmutablePoint {
final num x;
final num y;
const ImmutablePoint(this.x, this.y); // 常量构造函数
static final ImmutablePoint origin = const ImmutablePoint(0, 0); // 创建一个常量对象
}

注意,创建一个常量对象时,不使用 new constructor,而是使用 const constructor 。

工厂构造函数
前面介绍的构造函数都属于生成式的构造函数,即语言替你完成对象的创建和返回,你只需要在构造函数中对这个刚刚创建出来的对象做一些初始化工作即可。但有时候为了更加灵活地创建一个对象,比如返回一个之前已经创建过的缓存对象,那么生成式构造函数就不能满足要求了。工厂模式已经在其它语言中被广泛使用,一般是通过一个静态方法实现的,由你自己来负责创建对象并返回它。在 Dart 中,使用关键字 factory 就可以定义一个工厂构造函数,也是由你自己来负责创建对象并返回它。但好处是它依然使用 new constructor() 的形式,这样使用工厂构造函数和使用普通构造函数在形式上没有区别。

class Symbol {
final String name;
static Map<String, Symbol> _cache;

factory Symbol(String name) { // 通过 factory 关键字定义工厂构造函数
if (_cache == null) {
_cache = {};
}

if (_cache.containsKey(name)) {
return _cache[name];
} else {
final symbol = new Symbol._internal(name);
_cache[name] = symbol;
return symbol;
}
}

Symbol._internal(this.name);
}

工厂构造函数不仅可以返回一个缓存的对象,也可以返回一个类的子类型。所以一个抽象类/接口可以定义一个工厂构造函数,返回它的默认实现。比如在 Dart 的核心库中就普遍采用这种形式,Map 被认为是个接口,可以有多种实现,new Map() 会返回 Map 的一个默认实现的实例。其实,在工厂构造函数中你可以返回任何对象,不过在检查模式下要求返回一个类的子类型。
  评论这张
 
阅读(2632)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018