作为下一步,我们将向Element添加显示宽度和高度的方法,展示在代码10.2中。height方法返回contents里的行数。width方法返回***行的长度,或如果元素没有行记录,返回零。(也就是说你不能定义一个高度为零但宽度不为零的元素。)
- abstract class Element {
- def contents: Array[String]
- def height: Int = contents.length
- def width: Int = if (height == 0) 0 else contents(0).length
- }
代码 10.2 定义无参数方法width和height
51CTO编辑推荐:Scala编程语言专题
请注意Element的三个方法没一个有参数列表,甚至连个空列表都没有。例如,代之以:
方法定义了不加括号的:
- def width(): Int
这种无参数方法在Scala里是非常普通的。相对的,带有空括号的方法定义,如def height(): Int,被称为空括号方法:empty-paren method。推荐的惯例是在没有参数并且方法仅通过读含有对象的方式访问可变状态(专指其不改变可变状态)时,使用无参数方法。这个惯例支持统一访问原则:uniform access principle,Meyer,面向对象软件构造【Mey00】就是说客户代码不应受通过字段还是方法实现属性的决定的影响。例如,我们可以选择把width和height作为字段而不是方法来实现,只要简单地在每个实现里把def修改成val即可:
- def width: Int
两组定义从客户的观点来看是完全相同的。唯一的差别是与的访问或许稍微比方法调用要快,因为字段值在类被初始化的时候被预计算,而方法调用在每次调用的时候都要计算。换句话说,字段在每个Element对象上需要更多的内存空间。因此类的使用概况,属性表达成字段还是方法更好,决定了其实现,并且这个概况还可以随时改变。重点是Element类的客户不应在其内部实现改变的时候受影响。
- abstract class Element {
- def contents: Array[String]
- val height = contents.length
- val width =
- if (height == 0) 0 else contents(0).length
- }
特别是如果类的字段变成了访问函数,且访问函数是纯的,就是说它没有副作用并且不依赖于可变状态,那么类Element的客户不需要被重写。客户都不应该需要关心这些。
目前为止一切良好。但仍然有些琐碎的复杂的东西要去做以协同Java处理事情的方式。问题在于Java没有实现统一访问原则。因此Java里是string.length(),不是string.length(尽管是array.length,不是array.length())。不用说,这让人很困惑。
为了在这道缺口上架一座桥梁,Scala在遇到混合了无参数和空括号方法的情况时很大度。特别是,你可以用空括号方法重载无参数方法,并且反之亦可。你还可以在调用任何不带参数的方法时省略空的括号。例如,下面两行在Scala里都是合法的:
原则上Scala的函数调用中可以省略所有的空括号。然而,在调用的方法表达的超过其接收调用者对象的属性时,推荐仍然写一对空的括号。例如,如果方法执行了I/O,或写入可重新赋值的变量(var),或读出不是接受调用者的字段的var,无论是直接的还是非直接的通过使用可变对象,那么空括号是合适的。这种方式是让参数列表扮演一个可见的线索说明某些有趣的计算正通过调用被触发。例如:
- Array(1, 2, 3).toString
- "abc".length
- "hello".length // 没有副作用,所以无须()
- println() // ***别省略()
总结起来,Scala里定义不带参数也没有副作用的方法为无参数方法,也就是说,省略空的括号,是鼓励的风格。另一方面,永远不要定义没有括号的带副作用的方法,因为那样的话方法调用看上去会像选择一个字段。这样你的客户看到了副作用会很奇怪。相同地,当你调用带副作用的函数,请确信写这个调用的时候包括了空的括号。另一种考虑这个问题的方式是,如果你调用的函数执行了操作,使用括号,但如果仅提供了对某个属性的访问,省略括号。
【相关阅读】