Martee
Martee

Reputation: 77

case class extending trait with abstract type

I had a couple of case classes like that:

case class Size(id: Long, size: Int)

case class Title(id: Long, title: String)
.
.
.

I had like 10 of these with pretty much the same functions. I decided to combine them under a common trait, which led to something like this:

trait Property[T]{
    val value: T
}

trait PropertyPair[T]{
   val id: Long
   val prop: Property[T]
}

case class Size(id: Long, prop: Property[Int]) extends PropertyPair[Int] {
    def size: Int = prop.value
}

case class Title(id: Long, prop: Property[String]) extends PropertyPair[String] {
    def title: String = prop.value
} 

Though the code block above seems to be a good solution, and now I can actually define functions under the PropertyPair trait, but the code still smells.

  1. I need to include [T] three times for every property I add
  2. I need to explicitly add property name as an extra function to access it as though it is just a field in the case class.

Now to initialize Title i need to write

Title(101L, Property("Title")) 

instead of

Title(101L, "Title")

For some reason I am sure there is a much more elegant and less error-prone solution then the one I provided.

Upvotes: 1

Views: 1302

Answers (1)

laughedelic
laughedelic

Reputation: 6460

You don't need 2 levels and can replace trait with abstract class to make use of its constructor:

sealed abstract class Property[T](val prop: T) {
  val id: Long
}

case class Size(id: Long, size: Int) extends Property[Int](size)
case class Title(id: Long, title: String) extends Property[String](title)

Each of these cases has an id value which is required by the Property class, but as you want them to have different names for prop, you can just pass them to the Property constructor as the prop value.

Then it can be used as

val size = Size(101L, 42)
val title = Title(202L, "Foo")

This is a straightforward solution. For a more general case I would suggest you to do it like this:

sealed trait AnyProperty {
  val id: Long

  type Prop
  val prop: Prop
}

sealed abstract class Property[T](
  val prop: T
) extends AnyProperty {
  type Prop = T
}

(the rest is the same)

Advantages of this approach are that you can use the top trait to refer to any property, for example

def foo[P <: AnyProperty](p: P): Long = p.id
foo(size) // 101L

Of you can refer to the Prop type member, for example

def buh[P <: AnyProperty](p: P): P#Prop = p.prop
buh(title) // "Foo"

Upvotes: 3

Related Questions