2012-10-03 135 views
69

我有一個基於Squeryl的應用程序。我將模型定義爲案例類,主要是因爲我覺得方便的方法有複製方法。Scala案例類繼承

我有兩個嚴格相關的模型。這些字段是相同的,許多操作是相同的,並且它們將被存儲在相同的數據庫表中。 但是有一些行爲只有在兩種情況之一中才有意義,或者在兩種情況下都有意義,但是不同。

到目前爲止,我只使用了一個單獨的案例類,它帶有一個區分模型類型的標誌,並且所有基於模型類型而不同的方法都以if開頭。這很煩人,不太安全。

我想要做的是在祖先案例類中考慮常見行爲和領域,並讓它們繼承兩個實際模型。但是,據我所知,從Scala繼承的案例類繼承了,甚至被禁止,如果子類本身是一個案例類(不是我的情況)。

What are the problems and pitfalls I should be aware in inheriting from a case class? Does it make sense in my case to do so?

+1

難道你不能從非案例類繼承,還是擴展一個共同的特質? – Eduardo

+0

我不確定。這些字段在祖先中定義。我想根據這些字段獲得複製方法,平等等。如果我將父項聲明爲抽象類,將子項聲明爲案例類,它是否會考慮在父項中定義的帳戶參數? – Andrea

+0

我想不是,你必須在抽象父母(或特質)和目標案例類中定義道具。最後,很多人的樣板,但至少鍵入安全 – virtualeyes

回答

89

我的首選避免情況下的類繼承不重複的代碼的方式是有些顯而易見的:如果你想更細粒度的

abstract class Person { 
    def name: String 
    def age: Int 
    // address and other properties 
    // methods (ideally only accessors since it is a case class) 
} 

case class Employer(val name: String, val age: Int, val taxno: Int) 
    extends Person 

case class Employee(val name: String, val age: Int, val salary: Int) 
    extends Person 


:創建一個共同的(抽象)基類,將這些屬性分爲單獨的特徵:

trait Identifiable { def name: String } 
trait Locatable { def address: String } 
// trait Ages { def age: Int } 

case class Employer(val name: String, val address: String, val taxno: Int) 
    extends Identifiable 
    with Locatable 

case class Employee(val name: String, val address: String, val salary: Int) 
    extends Identifiable 
    with Locatable 
+48

你說的這個「沒有代碼重複」在哪裏?是的,案例類與其父母之間定義了合同,但您仍然在輸入道具X2 – virtualeyes

+2

@virtualeyes正確的是,您仍然需要重複屬性。但是,您不必重複使用方法,這通常相當於比屬性更多的代碼。 –

+1

是的,只是希望能夠解決屬性的重複問題 - 另一個答案暗示類型類是一種可能的解決方法;然而,不知道怎麼樣更像面向混合行爲,如性狀,但更靈活。只是樣板重:case類,可以忍受,就好像它是相當否則不可思議,真的可以砍出的屬性定義偉大的大片 – virtualeyes

12

案例類適用於值對象,即不會更改任何屬性並可與equals進行比較的對象。

但是,在繼承的情況下實現平等是相當複雜的。考慮一個兩類:

class Point(x : Int, y : Int) 

class ColoredPoint(x : Int, y : Int, c : Color) extends Point 

所以根據定義重點色(1,4,紅色)應該等於該點(1,4),它們是相同的點畢竟。所以ColorPoint(1,4,藍色)也應該等於Point(1,4),對吧?但是當然ColorPoint(1,4,紅色)不應該等於ColorPoint(1,4,藍色),因爲它們具有不同的顏色。你走了,平等關係的一個基本屬性被打破了。

更新

您可以使用繼承自性狀解決很多的問題,在另一個答案描述。更靈活的替代方案通常是使用類型類。見What are type classes in Scala useful for?http://www.youtube.com/watch?v=sVMES4RZF-8

+0

我理解並同意這一點。那麼,當你有一個處理僱主和僱員的應用程序時,你應該怎麼做?假設它們共享所有字段(名稱,地址等),唯一的區別在於某些方法 - 例如,可能需要定義「Employer.fire(e:Emplooyee)」,但不能相反。我想製作兩個不同的課程,因爲它們實際上代表了不同的對象,但我也不喜歡所出現的重複。 – Andrea

+0

得到了一個這裏的問題類型類方法的例子?即關於案例類別 – virtualeyes

+0

@virtualeyes對於各種類型的實體,可以有完全獨立的類型,並提供類型類來提供行爲。這些類型類可以儘可能地使用繼承,因爲它們不受案例類的語義契約的約束。這個問題會有用嗎?不知道,這個問題還不夠具體。 –

36

由於這對很多人來說都是一個有趣的話題,請讓我在這裏點亮一些。

你可以用下面的方法去:

// You can mark it as 'sealed'. Explained later. 
sealed trait Person { 
    def name: String 
} 

case class Employee(
    override val name: String, 
    salary: Int 
) extends Person 

case class Tourist(
    override val name: String, 
    bored: Boolean 
) extends Person 

是的,你要複製的字段。如果你不這樣做,簡直不可能實現正確的平等among other problems

但是,您不需要複製方法/函數。

如果幾個屬性的重複對你來說非常重要,那麼使用常規類,但是請記住它們不適合FP。

或者,你可以使用,而不是組成繼承:

case class Employee(
    person: Person, 
    salary: Int 
) 

// In code: 
val employee = ... 
println(employee.person.name) 

成分是有效的,並且你應該考慮,以及健全的策略。

如果你想知道密封特質是什麼意思 - 它只能在同一個文件中擴展。也就是說,上面的兩個案例類必須在同一個文件中。這允許詳盡的編譯器檢查:

val x = Employee(name = "Jack", salary = 50000) 

x match { 
    case Employee(name) => println(s"I'm $name!") 
} 

給出了一個錯誤:

warning: match is not exhaustive! 
missing combination   Tourist 

這是非常有用的。現在你不會忘記處理其他類型的Person(人)。這實際上就是Scala中的Option類。

如果這對你並不重要,那麼你可以使它不被密封,並將案例類放入他們自己的文件中。也許還有寫作。

+1

我認爲trait中的'def name'需要是'val name'。我的編譯器給了我前者無法訪問的代碼警告。 – BAR

3

在這種情況下,我傾向於使用組成,而不是繼承即

sealed trait IVehicle // tagging trait 

case class Vehicle(color: String) extends IVehicle 

case class Car(vehicle: Vehicle, doors: Int) extends IVehicle 

val vehicle: IVehicle = ... 

vehicle match { 
    case Car(Vehicle(color), doors) => println(s"$color car with $doors doors") 
    case Vehicle(color) => println(s"$color vehicle") 
} 

顯然,你可以使用更復雜的層次和比賽,但希望這給你的想法。關鍵是要利用案例類提供的嵌套提取器