使用特质与工厂模式

6
我目前正在学习Scala,想知道是否可以在工厂中使用traits。
我尝试了以下代码:
abstract class Foo {
  ...
}
object Foo {
  def apply() = new Bar

  private class Bar extends Foo {
    ...
  }
}
Foo() with MyTrait // 不起作用
我猜是因为with必须在new之前出现。
那么有没有其他方法可以做到这一点呢?
谢谢。

可能是重复的问题:如何在Scala的通用方法中创建一个Trait实例? - Ken Bloom
4个回答

4

不行了,当apply()方法返回时,实例已经被创建。

您可以在工厂方法中使用traits。下面的代码来自我正在编写的一个相当大的示例:

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 {
  ...
}

在这段代码中,Avatar的类型(职业和种族)是根据RaceType和CharacterType枚举在工厂中决定的。你所拥有的是一个工厂,可以处理各种不同类型或类型组合。请注意,本文中保留了HTML标签。

1
在你的情况下,我们必须预先知道可以使用的不同特征。但是,如果我们不知道,是否可以通过以“def applyT = new Foo with T”的定义将特征转移到工厂方法中? - Mr_Qqn
2
@Mr_Qqn:不,你不能这样做,即使通过传递一个清单也不行。如果你需要这种行为,请计划创建代理,将特定的特征代理到工厂创建的对象,并为那些特征创建(隐式)转换以创建必要的代理。 - Ken Bloom
1
谢谢,使用代理完美解决了问题。但是你所说的创建隐式转换是什么意思?new FooProxy with MyTrait 可以正确完成工作。 - Mr_Qqn
3
@Ken,我希望看到这样一个代理的代码示例。您能否在此线程中添加一个新的响应,并提供一个示例? - olle kullberg

3

假设你有以下内容:

class Foo
object Foo { def apply() = new Foo }
trait Baz

然后:

Foo() with Baz

类比于:

val foo = new Foo
foo with Baz

这意味着某种基于原型的继承,而Scala并没有这种继承方式。(据我所知)

(我猜测思考上的错误在于直觉地将等号视为“替换符号”。也就是说,由于Foo()表示Foo.apply(),并且“等于”new Foo,你可以用new Foo替换Foo()。但显然这是不可能的。)


3

隐式转换解决方案

Ken建议在这种情况下可以使用代理来帮助我们。我们想要做的是在创建实例后为其添加一个trait。如果有人编写了该类(和apply()方法),而你无法访问源代码,则此"猴子补丁"可能会很有用。在这种情况下,您可以通过隐式转换在实例上方添加代理/包装(无需手动转换):


使用Foo示例,我们可以这样做:

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!"))

输出:

I am really useful, not! 

我不喜欢这个例子的原因是:

Baz没有以任何方式使用Foo。很难看出为什么我们要将usefulMethod()附加到Foo


因此,我制作了一个新的示例,其中“猴子补丁”到实例中的特征实际上使用实例:

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

输出:

I am Xerxes, the Elf.

在这个例子中,添加的Elf特性使用了它所继承实例的getName方法。
如果您发现任何错误,我会非常感激,因为我还不是很擅长隐式转换。

我扩展了你的解决方案,以便支持像 val xerxes = Avatar[Elf]("Xerxes") 这样的代码。 - Aaron Novstrup

2

使用代理和隐式转换的解决方案

这个例子扩展了olle的解决方案,允许用户在调用apply()方法时指定mixin trait(例如: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)

打印:

我是精灵Xerxes。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接