0

I'm writing a message parser. Suppose I have a superclass Message with two auxiliary constructors, one that accepts String raw messages and one that accepts a Map with datafields mapped out in key-value pairs.

class Message {
  def this(s: String)
  def this(m: Map[String, String])

  def toRaw = { ... } # call third party lib to return the generated msg
  def map # call third party lib to return the parsed message

  def something1 # something common for all messages which would be overriden in child classes
  def something2 # something common for all messages which would be overriden in child classes
  ...
}

There's good reason to do this as the library that does parsing/generating is kind of awkward and removing the complexity of interfacing with it into a separate class makes sense, the child class would look something like this:

class SomeMessage extends Message {
  def something1 # ...
  def something2 # ...
}

and the idea is to use the overloaded constructors in the child class, for example:

val msg = new SomeMessage(rawMessage) # or
val msg = new SomeMessage("fld1" -> ".....", "fld2" -> "....")

# and then be able to call
msg.something1
msg.something2 # ...

However, the way auxiliary constructors and inheritance seem to behave in Scala this pattern has proven to be pretty challenging, and the simplest solution I found so far is to create a method called constructMe, which does the work of the constructors in the above case:

val msg = new SomeMessage
msg.constructMe(rawMessage) # or
msg.constructMe("fld1" -> ".....", "fld2" -> "....")

which seems crazy to need a method called constructMe.

So, the question:

is there a way to structure the code so to simply use the overloaded constructors from the superclass? For example:

val msg = new SomeMessage(rawMessage) # or
val msg = new SomeMessage("fld1" -> ".....", "fld2" -> "....")

or am I simply approaching the problem the wrong way?

bbozo
  • 7,075
  • 3
  • 30
  • 56

1 Answers1

1

Unless I'm missing something, you are calling the constructor like this:

val msg = new SomeMessage(rawMessage)

But the Message class doesn't not take a parameter, your class should be defined so:

class Message(val message: String) {
  def this(m: Map[String, String]) = this("some value from mapping")
} 

Also note that the constructor in scala must call the primary constructor as first action, see this question for more info.

And then the class extending the Message class should be like this:

class SomeMessage(val someString: String) extends Message(someString) {
  def this(m: Map[String, String]) = this("this is a SomeMessage")
}

Note that the constructor needs a code block otherwise your code won't compile, you can't have a definition like def this(someString: String) without providing the implementation.

Edit:

To be honest I don't quite get why you want to use Maps in your architecture, your class main point it to contain a String, having to do with complex types in constructors can lead to problems. Let's say you have some class which can take a Map[String, String] as a constructor parameter, what will you do with it? As I said a constructor must call himself as first instruction, what you could is something like this:

class A(someString: String) = {
  def this(map: Map[String, String]) = this(map.toString)
}

And that's it, the restrictions in scala don't allow you to do anything more, you would want to do some validation, for example let's say you want to take always the second element in the map, this could throw exceptions since the user is not forced to provide a map with more than one value, he's not even forced to provide a filled map unless you start filling your class with requires.

In your case I probably would leave String as class parameter or maybe a List[String] where you can call mkString or toString.

Anyway if you are satisfied calling map.toString you have to give both constructor implementation to parent and child class, this is one of scala constructor restrictions (in Java you could approach the problem in a different way), I hope somebody will prove me wrong, but as far as I know there's no other way to do it.

As a side note, I personally find this kind of restriction to be correct (most of the time) since the force you to structure your code to be more rigorous and have a better architecture, think about the fact that allowing people to do whatever they want in a constructor (like in java) obfuscate their true purpose, that is return a new instance of a class.

Community
  • 1
  • 1
Ende Neu
  • 15,581
  • 5
  • 57
  • 68
  • Thanks :) Issue is, I don't want to change the constructor in the child class, I want it to initialize the object the same way the constructors do it in the superclass – bbozo Jul 25 '14 at 14:37
  • In your question you stated you wanted to use overloaded method from the superclass, that means that `SomeMessage` should also have a constructor that takes a map, or do you want to use the constructor from the super class instead? – Ende Neu Jul 25 '14 at 14:44
  • The idea is to use the superclass constructors as constructor behavior is the same in `Message` and `SomeMessage` – bbozo Jul 25 '14 at 15:18
  • I don't think that's possible, you would have to define the constructor that takes a map in both children and parent class since in Scala you can't use `super`. – Ende Neu Jul 25 '14 at 15:22
  • Argh :) What would you suggest as "the scala way" to implement that kind of pattern? – bbozo Jul 25 '14 at 15:51
  • Thank you Ende :) Idea is that the `String` constructor initializes the `Message` object using an already generated message received through a socket and that the `Map` constructor is used to generate the `Message` based on the data fields the message should contain - they are not dependent on each other, they are just two relatively simple and equally valid entry points to correctly initialize the object. I have used this pattern for this exact purpose in other languages, and its the best approach I've found - I'm just not sure if it's scala-ish enough – bbozo Jul 25 '14 at 17:29
  • 1
    That was clear, but how do you plan to implement the constructor which takes a map? Simply calling `toString`on the map? If you really want to have a constructor which takes a map you could make `Message` abstract to avoid instantiation and leave the constructor implementation to the extending classes, as I said I don't see many options here since there isn't a way to explicitly refer to the parent class constructor. – Ende Neu Jul 25 '14 at 17:40