tsjnsn
tsjnsn

Reputation: 451

Overriding & inherited secondary constructors in Scala

Suppose I wanted to represent a Book in Scala, and it is generated directly from XML. I want a wrapping parent class XMLObject to encompass classes that can be directly mapped to and from XML.

Below is an example of a working implementation of this, but I want to know why constructors cannot be abstract and you can't use the override keyword, but you can still redefine a constructor with the same signature as its parent in a subclass, and have it work the way you would expect.

Is this considered "bad" coding practice, and if so, what would be a better way to get similar functionality?

abstract class XMLObject {
  def toXML:Node
  def this(xml:Node) = this()
}

class Book(

  val author:String = "",
  val title:String = "",
  val genre:String = "",
  val price:Double = 0,
  val publishDate:Date = null,
  val description:String = "",
  val id:Int = 0

  ) extends XMLObject {

  override def toXML:Node =
    <book id="{id}">
      ...
    </book>

  def this(xml:Node) = {
    this(
      author = (xml \ "author").text,
      title = (xml \ "title").text,
      genre = (xml \ "genre").text,
      price = (xml \ "price").text.toDouble,
      publishDate = (new SimpleDateFormat("yyyy-MM-dd")).parse((xml \ "publish_date").text),
      description = (xml \ "description").text
    )
  }
}

Example use:

val book = new Book(someXMLNode)

Upvotes: 2

Views: 167

Answers (2)

AmigoNico
AmigoNico

Reputation: 6852

I would use type classes for this.

The fact that you want to be able to map a Book (and other things) to and from XML is orthogonal to what those entities are. You don't want to choose a class hierarchy just based on the fact that you want these objects to have some XML mapping functionality in common. A proper superclass for Book might be PublishedEntity or something similar, but not XMLObject.

And what happens if next week you want to add JSON parsing/rendering? You've already used the superclass for XML; what would you do?

A better solution would be to make a trait for the XML interface and mix it in at the root. That way you could mix in as many such things as you want, and still be free to choose a sensible class hierarchy.

But an even better solution would be type classes, because they allow you to add support for someone else's class, that you can't add methods to.

Here is a slide presentation that Erik Osheim prepared on type classes.

Many of the JSON parser/formatter packages around (e.g. Spray's) use type classes. I haven't used XML much in Scala, but I would guess there are type class implementations for XML as well. A quick search turned up this.

Upvotes: 1

gzm0
gzm0

Reputation: 14842

A constructor can only be called in the form:

new X(...)

That means you know the runtime type of the object you are going to create. Meaning overriding makes no sense here. You can still define constructors in abstract classes for example, but this is for chaining (calling the superclass constructor in the classes constructor).

What you seem to be looking for is rather a factory pattern:

  1. Remove the constructor from XMLObject
  2. If you want, add a function to XMLObject's companion that decides based on the XML you pass in, what sub-class to create.

For example:

object XMLObject {
  def apply(xml: Node) = xml match {
    case <book> _ </book> => new Book(xml)
    // ...
    case _ => sys.error("malformed element")
  }
}

Upvotes: 1

Related Questions