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.