2013-03-15 8 views
9

У меня есть два класса PixelObject, ImageRefObject и еще несколько, но вот только эти два класса упрощают вещи. Все они являются подклассами trait Object, которые содержат uid. Мне нужен универсальный метод, который скопирует экземпляр класса case с заданным новым uid. Причина, в которой я нуждаюсь, потому что моя задача - создать класс ObjectRepository, который сохранит экземпляр любого подкласса Object и вернет его новым uid. Моя попытка:Scala copy case class с общим типом

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) 

, но я получаю следующее сообщение об ошибке:

➜ ~ 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 

ответ

1

Конечно, лучшее решение было бы на самом деле использовать подтипы?

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

Кастинг в A делает трюк - возможно, из-за рекурсивного определения классов вашего случая.

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

Может быть, есть более элегантное решение (кроме - хорошо реализации withUid для каждого случая класса, который я думаю, не, что вы просили), но это работает. :) Я думаю, что это может быть не простая идея сделать это с UidBuilder, но это интересный подход, тем не менее.

Чтобы убедиться, что вы не забыли дело - и я беру его все необходимые классы случае находятся в одной и той же единице компиляции в любом случае - сделать свой Object в sealed abstract class и добавить еще один бросок

this.asInstanceOf[Object] 

Если вы оставьте поле для одного из ваших классов дел, вы получите предупреждение.

8

Я бы придерживался решения, предложенного Seam. Я сделал то же самое пару месяцев назад. Например:

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

Таким образом, вместо того, определяя UuiBuilder спички для всех реализаций вашего признака, вы определяете метод в самой своей реализации. Вероятно, вы не хотите изменять UuiBuilder каждый раз, когда вы добавляете новую реализацию.

Кроме того, я также рекомендовал бы использовать самонастраивающийся текст для принудительного ввода возвращаемого типа вашего метода withId().