I'm currently discovering scala and I was wondering if I could use traits with a factory.
I tried this :
abstract class Foo {
...
}
object Foo {
def apply() = new Bar
private class Bar extends Foo {
...
}
}
Foo() with MyTrait // Not working
I guess it's because with
must be preceded by new
.
So is there any way to do this ?
Thank you
No it is too late, the instance is already created when the apply() method returns.
What you can do is using the traits inside the factory method. The code below is from a rather big code example I am writing:
object Avatar {
// Avatar factory method
def apply(name: String, race: RaceType.Value, character: CharacterType.Value
): Avatar = {
race match {
case RaceType.Dwarf => {
character match {
case CharacterType.Thief => new Avatar(name) with Dwarf with Thief
case CharacterType.Warrior => new Avatar(name) with Dwarf with Warrior
case CharacterType.Wizard => new Avatar(name) with Dwarf with Wizard
}
}
case RaceType.Elf => {
character match {
case CharacterType.Thief => new Avatar(name) with Elf with Thief
case CharacterType.Warrior => new Avatar(name) with Elf with Warrior
case CharacterType.Wizard => new Avatar(name) with Elf with Wizard
}
}
}
}
}
class Avatar(val name: String) extends Character {
...
}
In this code the type (profession and race) of your Avatar is decided in the factory based on the RaceType and CharacterType enumerations. What you have is one factory for all sorts of different types or type combinations.
Say you have:
class Foo
object Foo { def apply() = new Foo }
trait Baz
Then:
Foo() with Baz
would be analogous to:
val foo = new Foo
foo with Baz
which would imply some kind of prototype-based inheritance, which Scala doesn't have. (As far as I know.)
(I'd guess the error in thinking is intuitively mistaking the = sign for a "substitution sign". I.e. since Foo() means Foo.apply() and which "equals" new Foo, you can substitue Foo() with new Foo. Which obviously you can't.)
Solution with implicit conversion
Ken suggested that a proxy could help us in this case. What we are trying to do here is to add a trait to the instance after it is created. This "monkey patching" could be useful if someone else wrote the class (and the apply()
method) and you cannot access the source. In this case you can do is add a proxy/wrapper on top of the instance by implicit conversion (no manual conversion needed):
Using the Foo
example we could do this like this:
class Foo
object Foo { def apply() = new Foo }
trait Baz { def usefulMethod(s: String) = "I am really useful, "+ s }
// ---- Proxy/Wrapper ----
class FooWithBazProxy extends Foo with Baz
// --- Implicit conversion ---
implicit def foo2FooWithBazProxy(foo: Foo): FooWithBazProxy = new FooWithBazProxy
// --- Dummy testcode ---
val foo = Foo()
println(foo.usefulMethod("not!"))
Outputs:
I am really useful, not!
The reason I do not like this example is:
Baz
doesn't use Foo
in any way. It is hard to see the reason why we would want to attach the usefulMethod()
to Foo
.
So I made a new example where the the trait we "monkey patch" into the instance actually uses the instance:
// --------- Predefined types -----------
trait Race {
def getName: String
}
class Avatar(val name: String) extends Race{
override def getName = name
}
object Avatar{
def apply() = new Avatar("Xerxes")
}
// ---------- Your new trait -----------
trait Elf extends Race {
def whoAmI = "I am "+ getName + ", the Elf. "
}
// ---- Proxy/Wrapper ----
class AvatarElfProxy(override val name: String) extends Avatar(name) with Elf
// ---- Implicit conversion ----
implicit def avatar2AvatarElfProxy(Avatar: Avatar): AvatarElfProxy = new AvatarElfProxy(Avatar.name)
// --- Dummy testcode ---
val xerxes= Avatar()
println(xerxes.whoAmI)
Prints:
I am Xerxes, the Elf.
In this example the added Elf
trait use the getName
method of the instance it extends.
Would be grateful if you see any errors, I am not the good at implicits (yet).
Solution with proxies and implicit conversion
This example extends olle's solution to allow the user to specify the mixin trait when calling the apply()
method (e.g. val xerxes = Avatar[Elf]("Xerxes")
).
// ----- Predefined types -----
trait Race {
def whoAmI: String
}
class Avatar[R <: Race](val name: String)
object Avatar {
def apply[R <: Race](name: String) = new Avatar[R](name)
}
// ----- Generic proxy -----
class AvatarProxy[R <: Race](val avatar: Avatar[R])
implicit def proxy2Avatar[R <: Race](proxy: AvatarProxy[R]): Avatar[R] =
proxy.avatar
// ----- A new trait -----
trait Elf extends Race {
self: AvatarProxy[Elf] =>
def whoAmI = "I am " + self.name + ", the Elf."
}
implicit def avatar2Elf(avatar: Avatar[Elf]): AvatarProxy[Elf] with Elf =
new AvatarProxy[Elf](avatar) with Elf
// --- Test code -----
val xerxes = Avatar[Elf]("Xerxes")
println(xerxes.whoAmI)
Prints:
I am Xerxes, the Elf.