Gre*_*idt 9 scala field idiomatic
考虑Scala中的以下基类和派生类:
abstract class Base( val x : String )
final class Derived( x : String ) extends Base( "Base's " + x )
{
override def toString = x
}
Run Code Online (Sandbox Code Playgroud)
这里,Derived类参数的标识符'x'覆盖Base类的字段,因此调用toString如下:
println( new Derived( "string" ).toString )
Run Code Online (Sandbox Code Playgroud)
返回Derived值并给出结果"string".
因此,对'x'参数的引用会提示编译器自动在Derived上生成一个字段,该字段在调用toString时提供.这通常非常方便,但会导致字段的复制(我现在将字段存储在Base和Derived上),这可能是不合需要的.为了避免这种复制,我可以将Derived类参数从'x'重命名为其他内容,例如'_x':
abstract class Base( val x : String )
final class Derived( _x : String ) extends Base( "Base's " + _x )
{
override def toString = x
}
Run Code Online (Sandbox Code Playgroud)
现在调用toString返回"Base的字符串",这就是我想要的.不幸的是,代码现在看起来有些难看,使用命名参数来初始化类也变得不那么优雅了:
new Derived( _x = "string" )
Run Code Online (Sandbox Code Playgroud)
还存在忘记给派生类的初始化参数赋予不同名称并且无意中引用错误字段的风险(因为Base类实际上可能保持不同的值,所以不合需要).
有没有更好的办法?
编辑1:为了澄清,我真的只想要Base值; 导出的只是初始化基类字段所必需的.该示例仅引用它们来说明随后出现的问题.
编辑2:实际上,如果我使用vars而不是vals,那么示例会更清楚,因为这突出了稍后在基类中更改值的问题:
class Base( var x : Int ) { def increment() { x = x + 1 } }
class Derived( x : Int ) extends Base( x ) { override def toString = x.toString }
val derived = new Derived( 1 )
println( derived.toString ) // yields '1', as expected
derived.increment()
println( derived.toString ) // still '1', probably unexpected
Run Code Online (Sandbox Code Playgroud)
编辑3:如果派生类最终会隐藏基类字段,那么有一种方法可以抑制自动字段生成.看起来Scala编译器实际上可以设计为你做这件事,但当然这与"更近"标识符(Derived class''x')隐藏更多远程标识符(Base类')的更一般规则相矛盾. 'X').似乎一个相当不错的解决方案是像'noval'这样的修饰符,可能是这样的:
class Base( var x : Int ) { def increment() { x = x + 1 } }
class Derived( noval x : Int ) extends Base( x ) { override def toString = x.toString }
val derived = new Derived( 1 )
println( derived.toString ) // yields '1', as expected
derived.increment()
println( derived.toString ) // still '2', as expected
Run Code Online (Sandbox Code Playgroud)
避免重复该字段的惯用方法是编写
abstract class Base { val x: String }
final class Derived(val x: String) extends Base {
def toString = x
}
Run Code Online (Sandbox Code Playgroud)
但是,在您的版本中,您看起来确实需要第二个字段,因为您有两个不同的值.正如您正确指出的那样,给这些字段赋予相同的名称可能会导致混淆.
由于您实际上不需要构造函数之外的构造函数参数,因此您可以使用此方法(具有作为工厂的伴随模块的私有构造函数):
abstract class Base { val x: String }
final class Derived private (val x: String) extends Base {
def toString = x
}
object Derived {
def apply(x: String) = new Derived("Base " + x)
}
Run Code Online (Sandbox Code Playgroud)
由于基类是抽象的,因此看起来你真的不想在类中使用相关的支持字段val(或var)Base.相反,您只是想要保证在具体的子类中可以使用这样的东西.
在Java中,您可以使用访问器方法getX来实现此目的.
在Scala中,我们可以去一个更好的,丘壑,VAR和DEFS占据相同的命名空间,所以val可以用来实现一个抽象def(或覆盖混凝土def,如果这就是你的船浮筒).更正式地说,这被称为"统一访问原则"
abstract class Base{ def x: String }
class Derived(val x: String) extends Base {
override def toString = x
}
Run Code Online (Sandbox Code Playgroud)
如果你需要x通过引用来设置Base,你还需要声明"setter"(然后可以用a实现var):
abstract class Base {
def x: String
def x_=(s: String): Unit
}
class Derived(var x: String) extends Base {
override def toString = x
}
Run Code Online (Sandbox Code Playgroud)
(并不是说我会鼓励任何设计中的可变性;除非有一个特别令人信服的理由.默认情况下有太多理由支持不变性)
UPDATE
这种方法的好处是:
x 可以是完全合成的值,完全根据其他值(即您已知道半径的圆的面积)实现x 可以在类型层次结构中的任意深度实现,并且不必显式传递每个中间构造函数(每次具有不同的名称)Base可以实现为特征; 如果你愿意的话