I wrote a scala trait the other day with a "companion package" instead of the usual "companion object". I was pretty happy with how the code came together, so thought I'd write a quick post to my poor neglected bLog.
I often write a scala trait that defines an interface to some tool that manages access to a data repository or manipulates data in some way. I
usually define the interface traits to different tools
provided by some module "bla"
in a "bla.controller" sub-package. I define bla's
data model in a "model" sub-package, and classes that
simply support a tool's interface in the tool's companion object,
so something like this:
package bla
package controller
trait Tool {
def get( id:String ):model.Data
def search( query:String ):Tool.SearchResult
...
}
object Tool {
case class SearchResult( query:String, matches:Iterable[model.IndexEntry] ) {}
object SomeEnum extends Enumeration {
val A, B, C = Value
}
trait Builder {
var creds:Credentials = null
def creds( value:Credentials ):this.type = {creds = value; this }
var endPoint:URL = new URL( "http://default.end.point" )
def endPoint( value:URL ):this.type = { endPoint = value; this }
...
def build():Tool
}
/** Factory necessary if not running in IOC container - ugh */
object Factory extends Supplier[Builder] {
def get():Builder = internal.SimpleTool.Factory.get()
}
}
Anyway - that pattern works great most of the time, but the other
day I was working on a tool with several supporting types in the
companion object,
and the Tool.scala file was growing fatter than I like.
Fortunately - it turns out we can accomplish the same semantics
as a companion object with a companion package like this:
package bla
package controller
trait Tool { ... }
package Tool {
// ...
object Factory extends Supplier[Builder] { ... }
}
The companion package approach has the advantage that we
can split out some of the types defined in the companion package to
their own files, so we could have a bla/DataHelper/Factory.scala
like this:
package bla
package controller
package Tool
object Factory extends Supplier[Tool] { ... }
The companion package can define "static" constants in a
package object, and we can move the tool-specific implementation
classes to its own bla.controller.Tool.internal package
rather than the shared bla.controller.internal package.
Finally - changing companion object code to the
companion package pattern does not require changes to client code
(code that uses Tool) - just a recompile.
Anyway - I like the way things fit together with the companion package pattern.