Using traits with a factory

2019-04-27 11:41发布

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

4条回答
Emotional °昔
2楼-- · 2019-04-27 12:30

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.)

查看更多
做个烂人
3楼-- · 2019-04-27 12:32

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.

查看更多
可以哭但决不认输i
4楼-- · 2019-04-27 12:34

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).

查看更多
女痞
5楼-- · 2019-04-27 12:40

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.

查看更多
登录 后发表回答