成员
在创建实例的时候最好是使用构造函数而不是静态方法。
构造函数通常由 new
或者 const
调用,其主要目的就是返回该类的一个实例(或者至少实现了其接口)。
在 Dart 中你将不再需要用静态方法来创建一个实例。已命名的构造函数使你能够说明对象是如何构建的,并且工场构造函数允许你在合适的情况下构建子类以及子接口的实例。
尽管如此,一些采取一定技巧创建新对象的方法看起来“并不像构造函数”。比如,尽管 Uri.parse() 是一个静态方法,它却通过给定的参数创建了一个 Uri 对象。相应地,实现了 Builder pattern 的类看起来可能比使用静态方法的类要好一些。
不过,在大多数情况下,即使会显得程序冗长你也应该使用构造函数而不是静态方法。当其他人使用你的类来创建对象时,他们更希望能够使用构造函数按照常规方式来创建实例。
class Point { // good
num x, y;
Point(this.x, this.y);
Point.polar(num theta, num radius)
: x = radius * math.cos(theta),
y = radius * math.sin(theta);
}
class Point { // bad
num x, y;
Point(this.x, this.y);
static Point polar(num theta, num radius) {
return new Point(radius * math.cos(theta),
radius * math.sin(theta));
}
}
对于空的构造函数体,用 ;
来代替 {}
。
在 Dart 中,一个函数体为空的构造函数可以由分号来结尾。这对于 const 类型的构造函数来说是必要的。考虑到一致性以及简洁性,其他构造函数也应该做到这点。
class Point { // good
int x, y;
Point(this.x, this.y);
}
class Point { // bad
int x, y;
Point(this.x, this.y) {}
}
请务必将 super()
的调用放在构造函数初始化列表的最后。
字段的初始化将会按照它们在构造函数初始化列表中出现的顺序来进行。如果你把对 super()
的调用放在初始化列表的中间,那么父类中的初始化将会在子类初始化列表中剩余内容之前被初始化。
这并不是说父类构造函数体中的内容将会被执行。这往往是在所有的初始化都完成之后才执行,而与 super()
的位置无关。初始化列表的影响正在逐渐降低,所以 super()
在列表中的位置几乎没有什么影响。
最好养成将父类构造函数放在子类构造函数最后的习惯,这有利于提升代码的连贯性。并且当父类构造函数体运行的时候,其可读性也会增加,甚至对运行的效果也会有提升。
View(Style style, List children) // good
:_children = children,
super(style){
View(Style style, List children) // bad
:super(style),
-children = children {
对于更改属性值的操作,请调用 setter 来执行。
如果函数的名称是以 set
开头的,这通常意味着该函数可能是 setter 函数。更准确的说,对于下面这些情况使用 setter 来代替函数是比较好的选择:
- 采用单个参数。
- 改变了对象中的某个状态。
- 有一个相应的 getter。对于使用者来说,有一些可以修改但是却无法看见的状态是有些奇怪的。(该准则反过来则是不对的,设置了 getter 而不为它定义 setter 也是可以的)
- 是幂等的。使用同一个值来调用两次 setter 时,第二次调用应当不含有其他操作。
- 为了快速运行。使用者们期待像
foo.bar = value
这样的表达式,因为它们执行很快。
rect.width = 3; // good
button.visible = false;
你应当避免为了“安全”而将字段封装在 getter 和 setter 中。
在 Java 以及 C# 中,即便我们所需要的操作是直接对应字段的,通常情况下我们还是会将所有的字段都封装在 getter 和 setter 中(或者是 C# 的属性中)。事实上,如果你需要在成员上做更多操作,你可以不触及调用点。这是因为,在 Java 中调用一个 setter 函数和直接操作字段是不同的。而在 C# 中使用属性和直接操作字段是互不兼容的。
Dart 中并没有这种限制。字段以及 getter/setter 是完全相同的。你可以先使用类中的字段,然后在不改动任何使用了该字段的代码的情况下将其封装在 getter 以及 setter 内。
class Box { // good
var contents;
}
class Box { // bad
var _contents;
get contents => _contents;
set contents(value) {
_contents = value;
}
}
最好使用 public final 字段来而不是定义一个 private 字段然后为之设置一个 public setter。
如果你有一个代码之外可见的字段并且不会为它赋值(并且在构造函数之外你不会更改它),多数情况下最简单的方法就是直接让它作为 fianl
变量。
class Box { // good
final contents = [];
}
class Box { // bad
var _contents;
get contents => _contents;
}
当某个成员的函数体返回的是单个表达式的时候,请考虑一下使用 =>
来定义这些成员。
除了函数表达式之外,Dart 也允许你使用 =>
来定义类的成员。这对于定义一些计算并返回单值的简单成员来说是个好习惯。
get width => right - left; // good
bool ready(num time) => minTime == null || minTime <= time;
containsValue(String value) => getValues().contains(value);
即便函数体并不是单行的也可以使用 =>
,但是不过你觉得将当行表达式拆开成多行代码比较好,那么将整个函数体放在花括号中并显示声明 return
看起来会更好。
你应该避免为了使用常规接口而从函数中返回 this
。
对于连续的函数调用,使用函数级联是种好习惯。
var buffer = new StringBuffer() // good
..write("one")
..write("two")
..write("three");
var buffer = new StringBuffer(); // bad
buffer
.write("one")
.write("two")
.write("three");
你应该避免使用布尔类型的参数,除非它们的含义是非常明显的。
和其他类型不同,布尔值通常直接使用字面值而不是布尔变量。像数字我们通常会将其封装在命名的常量中,但是在使用的时候我们通常直接使用true
和 false
。这样其他人就很难明白你的调用语句中布尔值究竟意味着什么:
new Task(true); // bad
new Task(false);
new ListBox(false, true, true);
相反,在调用的时候,如果你使用命名的参数,命名的构造函数或者是命名后的常量看起来就很舒服。
new Task.oneShot(); // bad
new Task.repeating();
new ListBox(scroll: true, showScrollbars: true);