I have been finding the use of a container object still quite handy, despite use of the cake pattern. Mostly I have used it to return singletons and dependency injection. Using Scala 2.10 reflection, I have developed a simple container for my requirements. This has been handy in getting the basics of the new reflection libraries, in particular, generating an item from its constructor method. My other goal was to make the container functional, hence transparent and immutable, I believe I pretty much achieved that, though am open to criticism.
So first the container trait:
trait Container {
def get[T:TypeTag]:T
def get[T:TypeTag](args:Any*):T
}
This methods return an instance of Type T, the second of which allows external dependencies to be injected.
Next is a trait for binding classes. The idea is that a list of types or instances will be registered for the containers use. The bind methods will attach all base classes of the registered type or instance as well, excluding Any or Object. If the same class or base class has already been registered, the latest bind will dominate. Bind occurs in the constructor, making the result effectively immutable.
By using the function bindAt, only the specified, or instance level class will be registered against the instance.
So a binding trait extension looks something like this:
trait BoundElements extends Bindings {
bind[ClassA]
bind[ClassB]
bind(new InstanceA()]
bindAt[BaseClassB](new InstanceB()]
}
This is achieved by storing the bindings in private list buffers which are then access through a lazy evaluated immutables map.
The bindings trait is as follows:
trait Bindings {
private val bindingsBuffer = new ListBuffer[(TypeSymbol, (ClassSymbol, MethodSymbol))]
private val instanceBuffer = new ListBuffer[(TypeSymbol, Any)]
private val excluded:Set[Symbol] = Set(typeOf[Any].typeSymbol, typeOf[Object].typeSymbol)
protected lazy val registry:Map[TypeSymbol, (ClassSymbol, MethodSymbol)] = bindingsBuffer.toMap
protected lazy val instanceRegistry:Map[TypeSymbol, Any] = instanceBuffer.toMap
protected def bind[T:TypeTag]() {
val types = getBaseTypes[T]
val ctor = getConstructor[T]
bindingsBuffer ++= types.map((_ -> (typeOf[T].typeSymbol.asClass, ctor)))
}
protected def bind[T:TypeTag](instance:T) {
val types = getBaseTypes[T]
instanceBuffer ++= types.map((_ -> instance))
}
protected def bindAt[T:TypeTag](instance:T) {
instanceBuffer += (typeOf[T].typeSymbol.asType -> instance)
}
private def getBaseTypes[T:TypeTag] = {
val base = typeOf[T].baseClasses.map(_.asType)
base.filter(!excluded.contains(_))
}
private def getConstructor[T:TypeTag] = typeOf[T].members.filter(_.isMethod).map(_.asMethod).filter(_.isConstructor).head
}
The next trait is the dependency injector, it requires bindings and extends the container trait. In this case it will only use the first listed constructor. The Dependency injector constructs the required classes, or if they are already instances, provides them.
trait DependencyInjector extends Container {
this:Bindings =>
def get[T:TypeTag]:T = getInstance(typeOf[T].typeSymbol.asType).asInstanceOf[T]
def get[T:TypeTag](args:Any*):T = getInstance(typeOf[T].typeSymbol.asType, args.toSet).asInstanceOf[T]
protected def getInstance(typeSymbol:TypeSymbol, args:Set[Any] = Set.empty):Any =
args.find(a => currentMirror.reflect(a).symbol.toType <:< typeSymbol.toType).getOrElse{
instanceRegistry.getOrElse(typeSymbol, {
val (clazz, ctor) = registry(typeSymbol)
val ctorArgs = ctor.asMethod.paramss.head map (p => getInstance(p.typeSignature.typeSymbol.asType, args))
currentMirror.reflectClass(clazz).reflectConstructor(ctor).apply(ctorArgs:_*)
})
}
}
Now all the bits are in place, you can create a container by mixing in bindings into the dependency injector. You can mix in any number of bindings, however latter bindings will override any previously registered classes if found. I have been told this goes
against the spirit of mixins, which I kind of agree with, but I think it's still a nice way of doing the bindings.
So using the BoundElements trait you could create your container like:
object MyContainer extends DependencyInjector with BoundElements
And you would use it like
MyContainer.get[ClassA]
MyContainer.get[ClassB](new Date(), new InstanceC())
I am also using it to hold traits where I have used the cake pattern. However this assumes the cake layers don't require any additional variable wiring, or if they do, it will be on a singleton instance.
trait BindCake extends Bindings {
bind(new DefaultService with DefaultSecurity with DefaultLogging)
}
MyCakeContainer.get[Service]
It would be nice for it to be able to assemble the cake objects itself, but the only way I have seen that might be possible is using
twitters eval. I'm open to any ideas.
Also, please feel free to suggest any better ways of handling the mutability of the bindings.
Another thing to note is that the container instance will only exist in the class library with created it, not, say a core library which holds the primary traits and is shared amongst all other libraries.
Edit:
I have found cases where an a get might not work if there isn't a clear inheritance line. For example, if I registered say Map("value1" -> 123, "value2" -> "result"), and I performed a get with Map[String,Any], I would not get the map object returned. This is where I would need to do a check against something like:
a => classTag[U].runtimeClass.isAssignableFrom(a.getClass)