成员设计
最后更新于:2022-04-02 05:55:25
[TOC]
成员属于对象,可以是方法变量也可以是实例变量。
## 优先使用final字段和顶级变量。
不可变的状态 - 随着时间的推移不会改变 - 程序员更容易推理。最小化它们使用的可变状态量的类和库往往更容易维护。
当然,拥有可变数据通常很有用。但是,如果您不需要它,您的默认值应该是尽可能地创建字段和顶级变量为final。
## 请使用getter进行概念上访问属性的操作。
决定一个成员什么时候应该是getter,什么时候应该是方法,这是一个具有挑战性的、微妙的、但却是好的API设计的重要部分,因此这是一个很长的指导原则。其他一些语言的文化则回避取值器。他们只在操作与实际操作几乎完全相同的情况下才使用它们——它对完全依赖于对象的状态进行了极少量的计算。任何比这更复杂或更重量级的东西都会在名称后面跟随(),以表示“在这里需要计算!”因为a.后面有一个名称。意思是“字段”。
Dart不是那样的。在Dart中,所有带点的名称都是可能进行计算的成员调用。字段是特殊的——它们是由语言提供实现的getter。换句话说,在Dart中,getter不是“特别慢的字段”;字段是“特别快速的getter”。
即便如此,选择getter而不是方法会向调用者发送重要信号。粗略地说,信号是这个操作是“字段一样的”。就调用者所知,该操作至少在原则上可以使用字段实现。这意味着:
即便如此,选择方法上的getter会向调用者发送一个重要信号。大致的信号是操作是“像字段一样”。至少在原理上,操作可以使用字段来实现,只要调用者者知道。这意味着:
* 该操作不接受任何参数并返回结果。
* 调用者主要关心结果。如果你想调用者担心如何操作产生的结果比他们多一点,结果被生产,然后给操作描述工作一个动词的名字,使之方法。
但这并不意味着该操作必须是在为了一个getter特别快。IterableBase.length是O(n),那没关系。对于取值器进行重要计算是好的。但是如果它做了大量的工作,你可能需要通过使它成为一个描述其功能的动词的方法来引起他们的注意。
~~~
【bad】
connection.nextIncomingMessage; // Does network I/O.
expression.normalForm; // Could be exponential to calculate.
~~~
* 该操作没有用户可见的副作用。访问实际字段不会改变程序中的对象或任何其他状态。它不会产生输出,写入文件等。一个getter也不应该做那些事情。
“用户可见”部分很重要。取值器修改隐藏状态或产生带外副作用是很好的。Getters可以懒惰地计算并存储他们的结果,写入缓存,记录东西等。只要调用者不关心副作用,它可能就好了。
~~~
【bad】
stdout.newline; // Produces output.
list.clear; // Modifies object.
~~~
* 该操作是幂等的。“幂等”是一个奇怪的词,在这种情况下,基本上意味着每次调用操作多次会产生相同的结果,除非在这些调用之间明确修改某些状态。(显然,list.length如果在调用之间向列表中添加元素,则会产生不同的结果。)
这里的“相同结果”并不意味着getter必须在连续调用中逐字地生成相同的对象。要求这将迫使许多吸气者进行脆弱的缓存,这否定了使用取值器的全部意义。每次调用它时,吸气剂返回一个新的未来或列表是很常见的,而且非常好。重要的是,未来完成相同的值,列表包含相同的元素。
换句话说,结果值应该与调用者关心的方面相同。
~~~
【bad】
DateTime.now; // New result each time.
~~~
* 生成的对象不会公开所有原始对象的状态。 字段仅显示一个对象。如果您的操作返回一个公开原始对象的整个状态的结果,那么它最好作为一个to___()或一个as___()方法。
如果以上所有描述了您的操作,它应该是一个取值器。似乎很少有成员会幸免于难,但令人惊讶的是很多成员。许多操作只是对某些状态进行一些计算,其中大多数可以而且应该是getter。
~~~
rectangle.area;
collection.isEmpty;
button.canShow;
dataSet.minimumValue;
~~~
## 请将setter用于从概念上改变属性的操作。
在setter与方法之间做出决定类似于在getter与方法之间做出决定。在这两种情况下,操作应该是“类似字段”。
对于setter,“类似字段”意味着:
* 该操作采用单个参数,不会产生结果值。
* 该操作会更改对象中的某些状态。
* 该操作是幂等的。对于相同的值,使用相同的值调用相同的setter两次,就调用者而言,第二次不应该执行任何操作。在内部,也许你有一些缓存失效或记录正在进行。没关系。但从取值器的角度来看,似乎第二次取值什么也没做。
~~~
rectangle.width = 3;
button.visible = false;
~~~
## 不要在没有相应取值器的情况下定义设置器。
用户将getter和setter视为对象的可见属性。可以写入但未被看到的“dropbox”属性令人困惑,并且混淆了他们对属性如何工作的直觉。例如,没有getter的setter意味着你可以=用来修改它,但不是+=。
本指南也并不意味着你应该添加一个getter只是允许您要添加的设置器。对象通常不应该暴露出比他们需要更多的状态。如果某个对象的某个状态可以修改但不能以相同的方式公开,请改用方法。
> 这条规则有一个例外。一个 Angular组件类可以暴露是从模板调用以初始化该组件设置器。通常,这些setter不是要从Dart代码调用的,也不需要相应的getter。(如果从Dart代码中使用它们,它们应该有一个吸气剂。)
>
## 避免返回null从成员返回类型为bool,double,int,或num。
即使Dart中所有类型都可以为空,但用户认为这些类型几乎从不包含null,而小写名称则鼓励“Java原始”思维模式。
在API中有一个“可以为空的原语”类型可能偶尔会有用,例如,表示地图中某些键没有值,但这些应该很少见。
如果您确实有可能返回的此类成员null,请将其记录得非常清楚,包括null将返回的条件。
## 避免只是为了启用流畅的接口从方法中返回this值。
方法级联是链接方法调用的更好解决方案。
~~~
var buffer = StringBuffer()
..write('one')
..write('two')
..write('three');
~~~
以下是反例:
~~~
var buffer = StringBuffer()
.write('one')
.write('two')
.write('three');
~~~
';