2013-03-15 19 views
9

Mam dwie klasy PixelObject, ImageRefObject i kilka innych, ale tutaj są tylko te dwie klasy, aby uprościć rzeczy. Wszystkie są podklasami zbioru trait Object, który zawiera uid. Potrzebuję uniwersalnej metody, która skopiuje instancję klasy case z danym nowym uid. Przyczyna, której potrzebuję, ponieważ moim zadaniem jest stworzenie klasy ObjectRepository, która uratuje instancję dowolnej podklasy Object i zwróci ją nowym uid. Moja próba:Scala klasa kopii z generic type

trait Object { 
    val uid: Option[String] 
} 

trait UidBuilder[A <: Object] { 
    def withUid(uid: String): A = { 
    this match { 
     case x: PixelObject => x.copy(uid = Some(uid)) 
     case x: ImageRefObject => x.copy(uid = Some(uid)) 
    } 
    } 
} 

case class PixelObject(uid: Option[String], targetUrl: String) extends Object with UidBuilder[PixelObject] 

case class ImageRefObject(uid: Option[String], targetUrl: String, imageUrl: String) extends Object with UidBuilder[ImageRefObject] 

val pix = PixelObject(Some("oldUid"), "http://example.com") 

val newPix = pix.withUid("newUid") 

println(newPix.toString) 

ale otrzymuję następujący błąd:

➜ ~ scala /tmp/1.scala 
/tmp/1.scala:9: error: type mismatch; 
found : this.PixelObject 
required: A 
     case x: PixelObject => x.copy(uid = Some(uid)) 
           ^
/tmp/1.scala:10: error: type mismatch; 
found : this.ImageRefObject 
required: A 
     case x: ImageRefObject => x.copy(uid = Some(uid)) 
            ^
two errors found 

Odpowiedz

1

pewnością lepszym rozwiązaniem byłoby rzeczywiście wykorzystać Subtyping?

trait Object { 
    val uid: Option[String] 
    def withNewUID(newUid: String): Object 
} 
0

Casting do A załatwia sprawę - prawdopodobnie ze względu na rekurencyjną definicję klasach przypadków.

trait UidBuilder[A <: Object] { 
    def withUid(uid: String): A = { 
    this match { 
     case x: PixelObject => x.copy(uid = Some(uid)).asInstanceOf[A] 
     case x: ImageRefObject => x.copy(uid = Some(uid)).asInstanceOf[A] 
    } 
    } 
} 

Może jest bardziej eleganckie rozwiązanie (z wyjątkiem - również wdrożenie withUid dla każdej klasy przypadek, który moim zdaniem jest nie co prosiłeś), ale to działa. :) Myślę, że to może nie jest prosty pomysł robienia tego z UidBuilder, ale jest to jednak ciekawe podejście.

Aby upewnić się, że nie zapomnisz przypadek - i biorę je wszystkie potrzebne klas przypadków są w tej samej jednostce kompilacji i tak - dokonać Object się sealed abstract class i dodać kolejną obsadę

this.asInstanceOf[Object] 

Jeśli porzuć skrzynkę dla jednej z twoich klas przypadku, otrzymasz ostrzeżenie.

8

Będę trzymać się rozwiązania zaproponowanego przez Seam. Zrobiłem to samo kilka miesięcy temu. Na przykład:

trait Entity[E <: Entity[E]] { 
    // self-typing to E to force withId to return this type 
    self: E => def id: Option[Long] 
    def withId(id: Long): E 
} 
case class Foo extends Entity[Foo] { 
    def withId(id:Long) = this.copy(id = Some(id)) 
} 

Więc zamiast definiowania UuiBuilder z meczu dla wszystkich implementacjach swojej cecha definiuje metodę w samej implementacji. Prawdopodobnie nie chcesz modyfikować UuiBuilder za każdym razem, gdy dodajesz nową implementację.

Ponadto, poleciłbym również użycie samodzielnego pisania, aby wymusić typ zwrotu metody withId().

Powiązane problemy