【问题标题】:How to provide alternative implementation of abstract interface in Scala?如何在 Scala 中提供抽象接口的替代实现?
【发布时间】:2019-10-08 17:07:40
【问题描述】:

这是我面临的问题:

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

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

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

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 类 mixin Toggles 都需要一个 objectapply 工厂来返回自定义实例以用于测试和生产
  3. Cake-pattern 是一种反模式

您还有其他方法可以解决这个问题吗?谢谢!

【问题讨论】:

    标签: scala design-patterns


    【解决方案1】:

    我不确定所有这些间接的目的是什么。另外,您到底想测试什么。
    但是,一般的想法是只使用定义服务 API 的 Traits,以及这些的默认 (production) 实现。如果一个实现需要另一个服务,它应该依赖于它。
    对于测试,您创建依赖项的模拟并测试服务实现。

    例如:

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

    【讨论】:

      猜你喜欢
      • 2019-06-05
      • 2020-10-20
      • 1970-01-01
      • 1970-01-01
      • 2018-10-10
      • 1970-01-01
      • 2012-07-17
      • 2015-06-04
      • 2012-08-06
      相关资源
      最近更新 更多