2011-06-29 50 views
9

考慮以下基類和派生類Scala中:處理基類vs派生類字段名稱的習慣Scala方式?

abstract class Base(val x : String) 

    final class Derived(x : String) extends Base("Base's " + x) 
    { 
     override def toString = x 
    } 

這裏,標識符派生類參數「x」的覆蓋的基類的領域中,所以調用的toString這樣的:

println(new Derived("string").toString) 

返回Derived值並給出結果「string」。

因此對'x'參數的引用會提示編譯器自動在Derived上生成一個字段,該字段在對toString的調用中提供。這通常很方便,但會導致字段的複製(我現在將這個字段存儲在Base和Derived上),這可能是不可取的。爲了避免這種複製,我可以從「X」派生類參數重命名爲別的東西,比如「_x」:

abstract class Base(val x : String) 

    final class Derived(_x : String) extends Base("Base's " + _x) 
    { 
     override def toString = x 
    } 

現在到ToString返回「基地的弦」,這是我想要的電話。不幸的是,現在的代碼看起來有點醜,並使用命名參數初始化類也變得不那麼優雅:

​​

也有忘記給派生類初始化參數不同的名稱,並在不經意間提到的風險錯誤的字段(因爲Base類可能實際上持有不同的值而不受歡迎)。

有沒有更好的方法?

編輯1:爲了澄清,我真的只想要Base值;派生的對於初始化基類的字段來說似乎是必需的。該示例僅引用它們來說明隨後的問題。

編輯2:實際上,例如本來是清晰的,如果我用了瓦爾代替丘壑,因爲這凸顯與基類價值觀得到改變後的問題:

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 

編輯3:如果派生類將以其他方式隱藏基類字段,那麼有辦法抑制自動字段生成可能會很好。看起來Scala編譯器實際上可能是爲你設計的,但是這當然違背了更接近標準的規則(Derived類'x')隱藏更遠的標識符(Base類' 'X')。這似乎是一個相當不錯的解決辦法是像修改「諾瓦爾」,也許是這樣的:

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 
+1

我會提交一張增強票據,以抓取Edit2和Edit3案例併發出警告。這個問題不僅僅是用類構造函數,而且你也可以在子類中引入參數名稱。 – jsuereth

+0

@jsuereth如果兩個字段的值可能不同(即,如果您不只是將構造函數參數傳遞給基類),那麼即使帶有「val」也會很好。我沒有看到從Derived內引用Base.x的方法(即使使用自引用註釋)。 –

+0

如果x是一個val,那麼可以在Derived中引用super [Base] .x。否則,它不是一個真正的領域,只是'封閉在類的範圍內',所以它默默地被添加爲一個字段。我知道這個區別有點愚蠢,但圖像是將變量提升爲匿名函數構造函數的機制。 – jsuereth

回答

6

慣用的方式,以避免重複的領域是寫

abstract class Base { val x: String } 

final class Derived(val x: String) extends Base { 
    def toString = x 
} 

然而,在你的版本,它看起來像你實際上想要第二個領域,因爲你有兩個不同的值。正如您正確指出的那樣,給這些字段命名可能會導致混淆。

既然你不真正需要的構造外的構造函數的參數,你可以使用這種方法(與作爲一個工廠配套模塊私有構造):

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) 
} 
+0

那麼,我真的只想要Base值;派生的對於初始化基礎對象來說似乎是必需的。如果派生類會以其他方式隱藏基類字段,那麼有辦法抑制自動字段生成可能會很好。 –

+0

@Gregor我更新了我的答案,以顯示如何使用一個私有構造函數和一個伴侶模塊來獲取您之後的行爲。 –

+0

我的擔心是無意中在'Derived'類中自動生成的字段隱藏了'Base'字段,這仍然在您的示例中發生:'def toString = x'將返回'Derived'值,不會返回'Base'值(見上面我的Edit2和Edit3)。我想我的問題暗示了Scala的編譯器自動生成字段的方式中固有的錯誤的無意識源。避免它很容易(在'Derived'中選擇一個不同的參數名稱),但我只是想知道是否有更好的方法。 –

1

你可以試試這個:

abstract class Base(val x : String) 

final class Derived(_x : String) extends Base(_x) { 
    override val x = "Base's " + _x 
    override def toString = x 
} 

然後

println(new Derived("string").toString) 

打印你想要什麼

+0

好的,謝謝。正如我後面澄清的那樣,我確實只想要基本值,而不是派生的值。 –

1

您已經規定,工程

abstract class Base(val x : String) 

final class Derived(_x : String) extends Base("Base's " + _x) 
{ 
    override def toString = x 
} 

如果問題是_x不是一個好聽的名字,那麼你應該使用一個有意義的答案。 或者,可以按如下方式

abstract class Base(val _x : String) 

final class Derived(x : String) extends Base("Base's " + x) 
{ 
    override def toString = _x 
} 

聲明你的類現在你必須初始化Derived情況下,「好」的語法。

如果階是允許

的方式抑制自動場生成如果派生類,否則最終隱藏基類字段。

這對我來說似乎是一個非常低級的細節,你不想在代碼中處理。如果這可以安全地完成,編譯器應該爲你做。

+0

右鍵 - 不幸的是,Base對於Derived來說有意義。您通常會以「theX」或「myX」或「localX」或「initialX」這樣的人爲名稱結束,並不比「_x」更好。 –

6

基類是抽象的,它不看,好像你真的一個在Base類,以及相關支持字段val(或var)。相反,你只是想保證這樣的事情可以在具體的子類中使用。

在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 
} 

如果您需要x通過引用是可設置爲Base,還需要申報「二傳手」(然後可以與實現一個var):

abstract class Base { 
    def x: String 
    def x_=(s: String): Unit 
} 

class Derived(var x: String) extends Base { 
    override def toString = x 
} 

(不,我會永遠鼓勵易變性在任何設計中,除非有它特別引人注目的理由。有太多的理由爲有利於默認不變性)

UPDATE

這種方法的好處是:

  • x可能是一個完全綜合值,完全是在以下方面實現其他值 (即您已經知道半徑的圓的面積)
  • x可以在任意的dep TH在類型層次結構,並且不必 通過每個中間構造 (每次具有不同的名稱)
  • 這裏只有需要單個支持字段,所以沒有浪費內存
  • 由於它明確地傳遞現在不需要構造函數,Base可以作爲一個特徵來實現; 如果你這麼願意
+0

在原始問題中,Derived類構造函數在將參數分配給該字段之前,將參數(使用「Base」的「+ _」函數)轉換。 –

+0

@Aaron - 當然,但想必這是爲了在追蹤問題時消除這兩個支持字段的歧義。我指出,真的不應該有兩個... –

+0

請參閱OP的評論我的答案。我認爲問題的出現是因爲構造函數的參數與字段中存儲的值不同。否則,至少使用'val',創建第二個字段並不重要。 –

0

好吧,首先我想指出@Yuriy Zubarev的答案可能是你真正想要的。其次,我認爲這個問題可能在於你的設計。看一下這個。這是你的代碼的一部分:

extends Base("Base's " + _x) 

於是一些價值x進入你的派生類,並獲取與信息修改(在這種情況下"Base's " + ...)。你看到問題了嗎?爲什麼你的派生類型知道你的基類型應該知道的東西?這是我提出的解決方案。

abstract class Base { 
    // this works especially well if you have a var 
    // which is what you wanna have as you pointed out later. 
    var x: String 
    x = "Base's " + x 
} 

final class Derived(override var x: String) extends Base{ 
    override def toString = x 
} 

這可能聽起來很刺耳,但如果這種解決方案能幫助你自動解決問題,那就意味着你的設計不好。如果另一方面它沒有幫助,比我可能不正確地理解你的問題,因此馬上道歉。

+0

感謝您的回答。在'Base'類構造函數調用中組裝字符串只是一個快速入門,可以區分'x'和'_x'。我唯一真正關心的是無意識的陰影。在更一般的情況下,你的建議確實會更好。 –

+0

感謝您的評論。我已經開始擔心我的回答是一個完整的****;) – agilesteel

1

正如@jsuereth所建議的那樣,我爲Scala創建了一個增強選項,僅供參考,我希望能夠正確地總結討論內容。感謝您的所有輸入!該票券可以找到這裏,下面的內容:https://issues.scala-lang.org/browse/SI-4762

在派生類基類字段無意遮蔽,警告期望

問題出現時的(a)在派生類的類的參數使用相同的 符號作爲基類中的字段或函數,以及(b)隨後在派生類中訪問基類符號 。派生類參數 會導致編譯器自動生成一個名稱相同的字段,該字段會影響基類符號 。 (Derived)類中的任何引用 旨在引用基類字段,然後(a)導致 字段的重複定義和(b)意外地(但正確地)將 引用到自動生成的字段中派生類。

代碼例如:

class Base(val x : String) 
class Derived(x : String) extends Base(x) { override def toString = x } 

因爲「基地」類有「VAL」來生成字段和派生類 不,顯影劑明確打算用唯一的「衍生」的「x」對於 傳遞到Base,因此預計'toString'中的引用爲 將產生Base值。相反,'toString'中的引用會導致編譯器 自動生成一個字段'Derived.x',該字段會隱藏'Base.x'字段 導致錯誤。編譯器的行爲正確,但結果是編程錯誤 。

使用場景(與風險更清晰的插圖VAR字段):

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 

由於這個問題出現每當有人使用默認的方法,從Scala中的一個派生類初始化 基類的字段,場景會出現 是非常常見的,並且會導致很多編程錯誤(對於較新的用戶很容易修復,但仍然是)。

存在此問題的簡單解決方法(使用派生的 類參數的不同名稱,如'_x','theX','initialX'等),但這會引入不需要的額外符號。

溶液A(最小):問題每當編譯器推斷,一個 類參數需要在派生類自動生成的字段 將陰影在基類中已定義的符號的警告。

解決方案B:的變通,仍然需要用溶液A,就是要拿出 ,每次一個初始化一個基類領域一個新的符號名。這個 的情況一直出現,並且用變通辦法 來污染命名空間,像'_x'和'theX'這樣的字段名稱似乎是不可取的。相反,如果開發人員 確定派生類符號否則最終將隱藏基類 類符號(例如,在解決方案A的警告之後),那麼可能很好的方法是禁止自動字段生成。也許有用 除了斯卡拉會像「諾瓦爾」(或「直通」或 「臨時」,或什麼 - 除了「VAL」和「VAR」)改性劑,是這樣的:

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 
0

@Gregor沙伊特 如果我移動的toString()下降到派生,如下面的代碼不起作用:

object Test { 
    abstract class Base (val x: String) 

    final class Derived(x: String) extends Base(x + " base") { 
    override def toString() = x 
    } 

    def main(args: Array[String]): Unit = { 
    val d = new Derived("hello") 
    println(d) // hello 
    } 
} 

A post from the official site said

的參數如類Foo(X:智力)變成一個領域,如果它是在一個或多個方法

引用 和馬丁的回答證實了其真實性:

這就是真實的,但它應該被視爲作爲實施方式 技術。這就是爲什麼規範對此保持沉默。

,因爲沒有辦法阻止編譯器的作用,我的選擇是一個更強大的方式來引用基類領域是使用下劃線來使用不同的名稱,如「_」爲前綴,如以下內容:

object Test { 
    abstract class Base (val x: String) 

    final class Derived(_x: String) extends Base(_x + " base") { 
    override def toString() = x 
    } 

    def main(args: Array[String]): Unit = { 
    val d = new Derived("hello") 
    println(d) // hello 
    } 
}