如何在 Scala 中提供抽象接口的替代实现?



这是我面临的问题:

我有一个抽象特征Toggles它有一个抽象的方法isToggleEnabled(name: String).

我想要的是能够将生产环境特征与isToggleEnabled的具体实现ProdToggles并在生产代码中使用它,但能够在测试时提供具有新实现isToggleEnabled的覆盖替代特征TestToggles

原因是生产环境实现使用 System 属性来存储切换名称及其状态,但是在测试中,我想提供不同的实现来存储和读取切换,以便多个测试可以并行运行而不会相互影响(就像通过系统属性一样(。我用蛋糕图案想出了什么:

trait Toggleable {
def isToggleEnabled(name: String): Boolean
}
trait ProdToggles extends Toggles with Toggleable {
override def isToggleEnabled(name: String): Boolean = System.getProperty("name").toBoolean
}
trait TestToggles extends Toggles with Toggleable {
val cache = scala.collection.mutable.HashMap.empty[String, Boolean]
override def isToggleEnabled(name: String): Boolean = cache.getOrElse(name, false)
}
trait Toggles {
this: Toggleable =>
def isEnabled(name: String): Boolean = {
isToggleEnabled(name)
}
}
//—————IN PROD code—————
class Prod() {
this: Toggles =>
def doSomething(): Unit ={
isEnabled("toggle.name")
}
}
object Prod {
def apply(): Prod = new Prod with ProdToggles
def apply(testing: Boolean) = new Prod with TestToggles
}
//——————IN TEST code———————————
class Tests {
val prod = Prod(true)
prod.doSomething()
}

但是,问题在于:

  1. 中断封装,Prod实例可能会被误用为Toggle实例,因为您可以这样做(new Prod with ProdToggles).{isEnabled, isToggleEnabled, doSomething}
  2. 每个Prod类混合Toggles都需要与工厂applyobject,以返回自定义实例进行测试和生产。
  3. 蛋糕图案是一种反图案

您还有其他方法来解决此问题吗?谢谢!

我不确定所有这些间接的目的是什么。另外,您究竟要测试什么。
但是,一般的想法是只具有定义服务API的特征,以及这些特征的默认(生产(实现。如果一个实现需要另一个服务,它应该依赖于它。
为了进行测试,您可以创建依赖项的模拟并测试服务实现。

例如:

// Toggleable.scala
trait Toggleable {
def isToggleEnabled(name: String): Boolean
}
object Toggleable {
final val impl: Toggleable =
new Toggleable {
override final def isToggleEnabled(name: String): Boolean =
System.getProperty("name").toBoolea
}
}
// Toggles.scala
trait Toggles {
def isEnabled(name: String): Boolean =
}
object Toggles {
final def impl(toggleable: Toggleable): Toggles =
new Toggles {
override final def isEnabled(name: String): Boolean =
toggleable.isToggleEnabled(name)
}
}
// Env.scala
trait Env {
def doSomething(): Unit
}
object Env {
final def prod(toggles: Toggles): Env =
new Env {
override final def doSomething(): Unit =
println(toggles.isEnabled("toggle.name"))
}
}
// Tests
object TestToggleable extends Toggleable {
private val cache =
scala.collection.mutable.HashMap.empty[String, Boolean]
override final def isToggleEnabled(name: String): Boolean =
cache.getOrElse(name, false)
}

class Tests {
val toggles = Toggles.impl(toggleable = TestToggleable)
assert(toggles.isEnabled("balh") == false)
val env = Env.prod(toggles)
prod.doSomething() // Not sure how do you test an Unit.
}

最新更新