I hold a reference to an instance of XStream class somewhere in my app. It is responsible for marshalling objects and it looks like this (examples are shortened for clarity):
val XML = new XStream(new xml.JDomDriver()) {{
setMode(XStream.NO_REFERENCES);
alias("courses", ScalaCollectionClasses.ListBuffer)
alias("course", classOf[resources.Course])
}}
That looks nice - the initialisation block gives the instantiation of XStream object nice DSLish look of a spec. But when I decided to use both XML and JSON and following chunk of code appeared on the screen.
val XML = new XStream(new xml.JDomDriver()) {{
setMode(XStream.NO_REFERENCES);
alias("courses", ScalaCollectionClasses.ListBuffer)
alias("course", classOf[resources.Course])
}}
val JSON = new XStream(new json.JsonHierarchicalStreamDriver()) {{
setMode(XStream.NO_REFERENCES);
alias("courses", ScalaCollectionClasses.ListBuffer)
alias("course", classOf[resources.Course])
}}
Ok.. duplication strikes you right away. So how do you fight that monster? There are actually a few options to choose from:
- inheritance
class MyXStream extends XStream {
setMode(XStream.NO_REFERENCES);
alias("courses", ScalaCollectionClasses.ListBuffer)
alias("course", classOf[resources.Course])
}
val XML = new MyXStream(new xml.JDomDriver())
val JSON = new MyXStream(new json.JsonHierarchicalStreamDriver()) - delegation
val XML = new XStream(new xml.JDomDriver()) {{ configure(this) }}
val JSON = new XStream(new json.JsonHierarchicalStreamDriver()) {{ configure(this) }}
def configure(xstream : XStream) = {
setMode(XStream.NO_REFERENCES);
alias("courses", ScalaCollectionClasses.ListBuffer)
alias("course", classOf[resources.Course])
}
or
val XML = configure(new XStream(new xml.JDomDriver()))
val JSON = configure(new XStream(new json.JsonHierarchicalStreamDriver()))
def configure(xstream : XStream) = {
xstream.setMode(XStream.NO_REFERENCES);
xstream.alias("courses", ScalaCollectionClasses.ListBuffer)
xstream.alias("course", classOf[resources.Course])
xstream
} - implicit conversion
val XML = new XStream(new xml.JDomDriver()).configure()
val JSON = new XStream(new json.JsonHierarchicalStreamDriver()).configure()
def xstreamToConfigurableXStream(xstream : XStream) = new {
def configure() = {
setMode(XStream.NO_REFERENCES);
alias("courses", ScalaCollectionClasses.ListBuffer)
alias("course", classOf[resources.Course])
}
}
Ok as for the first one - inheritance sux.. I mean really.. inheritance is for most of the time the last resort and you know it.
Second and the third one are ok.. I mean most of the time I'd say ok to this solution and implement something like this in Java.
Fourth one.. imho this is a hack and clear abuse of implicit conversion in Scala. Personally I believe that the use of implicit conversion can be justified when trying to bend the existing API for your purposes (ie. trying to seamlessly extend non-functional Java classes to full-blown Scala versions).
So what is the best solution I've found?
trait Configuration
/* 1. */ trait DefaultConfiguration extends Configuration { /* 3. */ this: XStream =>
/* 4. begin */
setMode(XStream.NO_REFERENCES);
alias("courses", ScalaCollectionClasses.ListBuffer)
alias("course", classOf[resources.Course])
/* 4. end */
}
/* 2. */val XML = new XStream(new xml.JDomDriver()) with DefaultConfiguration;
val JSON = new XStream(new json.JsonHierarchicalStreamDriver()) with DefaultConfiguration;
- Create a trait that will bind the reference to an instance of a class it extends to "this"
- Create an instance XStream for XML marshalling and extend with 'DefaultConfiguration' trait which..
- in fact will bind the instance of XStream to 'this' and..
- perform an initialisation of a bound object
A powerful (and strikingly easy after you see it for the first time and get the 'aha moment') mechanism for introducing behavior into existing classes. It gives you a perfect decoupling and let you introduce an explicit abstraction for the extension ('configuration' in this case). And now I can do:
def foo(xstream : XStream with Configuration) = "thanks for configured xstream instance bro"
Which basically means: "give me an xstream instance.. but the configured one if you may!"
I am not sure I have fully explained the reason why I like the last one the most.. I got the gut-feeling this is the right path to go. Reusable, expressive, explicit.
Damn damn damn! I wanted to prepare presentation about Scala, but now I know I know very little about it. I have to read a little more about traits... Can you recommend some site?
ReplyDeleteI went through all interesting scala blogs I read and picked a few interesting posts:
ReplyDeleteScala trivia of the day: Traits can extend classes
How should traits in Scala be used?
Scala - To DI or not to DI
There was also a lot of interesting stuff having been said by Jonas on his blog but I'm too lazy to look it up - sorry ;)
Jonas Boner blog
Also if you haven't seen Cope's presentation on DCI you should totally check it out - mind blowing :)
Thanks for the links! I'll read them. I hope the part marked with /* 3 */ is explained there, this is something I don't really get yet. This binding XStream to "this".
ReplyDeleteGreat post Pawel, I like the idea.
ReplyDeleteI'm struggling with a similar challenge and wondered if you'd thought about it.
In your code for DefaultConfiguration, you add aliases for two classes. Say you now create another class, which also needs an alias, I think you'll need to modify DefaultConfiguration to include that alias.
I'm trying to find a way to keep the aliases (and any other per-class config) with the classes themselves, and inject them into the stream so to speak.
thoughts?
Unfortunately I did only use xstream this one time, so I forgot a lot till now. If I however find some time (I'm busy with my MSc project currently ;)) to investigate that (surely sounds like interesting question) I'd surely send you feedback.
ReplyDelete