2016-10-26 35 views
0

我在SO上反覆閱讀,因爲案例類默認實現了一個等式方法,因此導致了相等問題,因此不應該擴展案例類。但是,如果一個特質擴展了一個case類,那麼這是否也有問題?從技術角度看,五月混合特徵可以擴展案例類別嗎?

case class MyCaseClass(string: String) 
trait MyTrait extends MyCaseClass 
val myCT = new MyCaseClass("hi") with MyTrait 

我想這歸結爲問題,MyTrait是否僅強制爲可混合只進MyCaseClass的實例化或是否MyTrait爲繼承MyTrait的類成員(字段值和方法),從而覆蓋它們。在第一種情況下,從MyCaseClass繼承是可以的,在後一種情況下它不會好。但是哪一個呢?

研究,我提出我的實驗

trait MyTrait extends MyCaseClass { 
    def equals(m: MyCaseClass): Boolean = false 
    def equals(m: MyCaseClass with MyTrait): Boolean = false 
} 
val myC = new MyCaseClass("hi") 
myCT.equals(myC) // res0: Boolean = true 

讓我相信,MyCaseClass的平等使用,MyTrait的不是一個。這表明,一個特質可以擴展一個case類是可以的(儘管類不能擴展case類)。

但是,我不確定我的實驗是否合法。你能否就這個問題提出一些看法?

+2

在我看來,案例類有專門的意義。它們意味着你可以用一個簡潔和無鍋爐的方式爲你的域對象建模。他們提供散列碼,平等,適用和不適用,這些都有助於我們輕鬆地對這些對象進行模式匹配。有一個'特質'繼承一個case類會感到奇怪和不自然。話雖如此,這個問題通常感覺像[XY問題](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)。你想達到什麼目的? –

+0

http://stackoverflow.com/questions/12854941/why-can-a-scala-trait-extend-a-class – dk14

+0

@YuvalItzchakov:我有一個包含數據的類。根據(複雜的)規則,它們是不同種類的,由哪些特徵混合而成。根據這些特徵,預計會出現不同的行爲。因此,我的特徵得到了行爲,並擴展了主數據對象,使它們變得豐富。 (我知道有一個關於[豐富vs貧血模型]的討論(http://stackoverflow.com/q/23314330/4533188),但[我贊成豐富的方法](http://www.martinfowler.com /bliki/AnemicDomainModel.html)。)問題是我可以讓我的數據對象成爲一個案例類,還是把它作爲一個普通的類。 – Make42

回答

3

基本上,性狀可以擴展爲any class,所以最好將它們用於常規類(OOP風格)。

反正你平等的合同仍然是斷開的,無論你的絕招​​(注意,標準Java的equals定義上Any,所使用的默認情況下,讓我們在HashMap甚至==說):

scala> trait MyTrait extends MyCaseClass { 
    | override def equals(m: Any): Boolean = false 
    | } 
defined trait MyTrait 

scala> val myCT = new MyCaseClass("hi") with MyTrait 
myCT: MyCaseClass with MyTrait = MyCaseClass(hi) 

scala> val myC = new MyCaseClass("hi") 
myC: MyCaseClass = MyCaseClass(hi) 

scala> myC.equals(myCT) 
res4: Boolean = true 

scala> myCT.equals(myC) 
res5: Boolean = false 

此外, Hashcode/equals是不是唯一的原因...

與其他類擴展case class是不自然的,因爲case class代表ADT所以它僅機型數據 - 不行爲。

這就是爲什麼你不應該添加任何方法(在OOD條款case class es是專爲貧血的方法)。因此,排除方法後 - 一個特點,只能用類混合變成廢話的使用特點與case類的點是模擬析取(這樣特質的接口在這裏 - 不混合插件):

//your data model (Haskell-like): 
data Color = Red | Blue 

//Scala 
trait Color 
case object Red extends Color 
case object Blue extends Color 

如果Color只能用Blue混合 - 這是一樣

data Color = Blue 

即使你需要更復雜的數據,像

//your data model (Haskell-like): 
data Color = BlueLike | RedLike 
data BlueLike = Blue | LightBlue 
data RedLike = Red | Pink 

//Scala 
trait Color extends Red 
trait BlueLike extends Color 
trait RedLike extends Color 
case class Red(name: String) extends RedLike //is OK 
case class Blue(name: String) extends BlueLike //won't compile!! 

結合Color只有Red似乎不是一個好方法(一般),因爲你將無法case object Blue extends BlueLike

P.S.案例類不打算用於OOP風格(混入是OOP的一部分) - 它們與類型類/模式匹配更好地交互。所以我建議將你的複雜類似方法的邏輯從case class中移走。一種方法可以是:

trait MyCaseClassLogic1 { 
    def applyLogic(cc: MyCaseClass, param: String) = {} 
} 

trait MyCaseClassLogic2 extends MyCaseClassLogic { 
    def applyLogic2(cc: MyCaseClass, param: String) = {} 
} 

object MyCaseClassLogic extends MyCaseClassLogic1 with MyCaseClassLogic2 

你可以使用自助型或trait extends在這裏,但你可以很容易地發現,這是多餘的,因爲applyLogic勢必MyCaseClass只:)

另一種方法是implicit class(或者你可以嘗試更類似類型的先進的東西)

implicit class MyCaseClassLogic(o: MyCaseClass) { 
    def applyLogic = {} 
} 

PS2貧血與豐富。因爲它適用於不可變(無狀態)數據,所以它不是精確的貧血模型。如果您閱讀the article,Martin Fowler的方法是OOP/OOD,默認情況下是有狀態的 - 這就是他在文章的大部分部分中所暗示的,暗示服務層和業務層應該具有不同的狀態。在FP中(至少在我的實踐中),我們仍然將領域邏輯與服務邏輯分開,但是我們也將操作與數據分開(在每一層中),這是另一回事。

+0

「基本上,特質可以擴展任何類(不僅僅是案例類)」 - 我的問題是相反的:當然它可以擴展任何類,但它可以擴展一個類,但是它可以(如在良好實踐中,技術上不可行) *即使**這是一個案例課程? – Make42

+0

仔細閱讀我的答案 - 這並不好 - 因爲你需要一個新班級來擴展你的班級(爲了使用你的特質),或者你不能在沒有這種班級的情況下使用你的特質。此外,關於合同你是不對的(你的例子也是錯誤的) - 正如你可以看到'equals'被破壞 – dk14

+0

而'case class'通常沒有方法,它應該只模擬** data ** not行爲 – dk14

1

擴展大小寫類是一種不好的做法(通常),因爲它具有實際意義 - 「數據容器」(POJO/ADT)。例如,Kotlin不允許這樣做。

另外,如果你真的想一些特徵來擴展類的情況下,你最好使用requires依賴(以避免情況下類繼承的陷阱):

scala> case class A() 
defined class A 

scala> trait B { self: A => } 
defined trait B 

scala> new B{} 
<console>:15: error: illegal inheritance; 
self-type B does not conform to B's selftype B with A 
    new B{} 
+0

這在我的情況下是不可能的。 MyTrait稍後將作爲一種類型提供給(完全其他)方法。這些其他方法也需要訪問數據字段(在MyClass中聲明)。因此MyTrait **需要**來擴展MyClass。 – Make42

+0

@ Make42想烤一些蛋糕嗎?我的意思是,'scala'蛋糕模式。 – dveim

+0

不是真的 - 我餓了(得讓我做三明治);-)。我有一個數據對象D,它必須是'D,其中A1'與B1'或'D與A2與B1'或'D與A2與B1'或'D與A2與B2'。具有屬性(特徵)的地方也在增加功能。它類似於一個蛋糕,但不一樣。 – Make42