关于java:带有继承和mixins的Scala可测试代码

Scala testable code with inheritance and mixins

我在Java中开发了很多代码,涉足Groovy和Haskell,现在已经把我带到了斯卡拉。

我对Scala的功能方面感到比较舒服,但是我发现自己在Scala中面向对象的设计有点不稳定,因为它感觉到Java有点不同,特别是由于特性/混合INS。

我的目标是编写尽可能可测试的代码,这些代码在我的Java开发中一直被翻译成焦点。

  • 尽可能不可变
  • 更喜欢由构造函数注入状态
  • 总是选择构图而不是继承(严重受此帖的影响,而且很可能是对此帖的过度反应)

现在,我正试图在这个新的斯卡拉领域站稳脚跟,我很难弄清楚我应该在这里采用什么方法,特别是我是否应该开始将继承用于某些目的。

编程scala(wampler和payne;o'reilly,第二版)有一个考虑因素的部分("良好的面向对象设计:一个离题"),我已经阅读了很多这样的文章,但我没有看到明确提到设计考虑的可测试性。本书提供了使用继承的建议:

  • An abstract base class or trait is subclassed one level by concrete classes, including case classes.
  • Concrete classes are never subclassed, except for two cases:

    • Classes that mix in other behaviors defined in traits (...)
    • Test-only versions to promote automated unit teting.
  • When subclassing seems like the right approach, consider partitioning behaviors into traits and mix in those traits instead.
  • Never split logical state across parent-child type boundaries.
  • 一些挖掘表明,有时混合比合成更好。

    所以本质上我有两个问题:

  • 在常见的情况下,即使考虑到可测试性,使用继承是否更好?

  • mix-in是否提供了增强代码可测试性的好方法?


  • 你提到的Q/A中的特征用法实际上是处理混合特征所提供的灵活性。

    在示例中,当显式扩展特性时,编译器会在编译时锁定类和超级类的类型。在这个例子中,myservice是一个LockingFlavorA

    1
    2
    3
    4
    5
    6
    7
    trait Locking { // ... }

    class LockingFlavorA extends Locking { //... }

    class MyService extends LockingFlavorA {

    }

    当您使用键入的自引用(如您所指的q/a中所示)时:

    1
    2
    3
    class MyService {
       this: Locking =>
    }

    Locking可指Locking本身,或Locking的任何有效子类。然后,作者混合调用站点的锁定实现,而不显式为此创建新类:

    1
    val myService: MyService = new MyService with JDK15Locking

    我认为当他们说你可以减轻测试时,他们真的在谈论使用这个功能来模仿Java开发人员通常对组合和模拟对象所做的事情。您只需制作一个模拟的Locking实现,并在测试期间将其混合在一起,然后为运行时制作一个真正的实现。

    对于您的问题:这是比使用模拟库和依赖注入更好还是更差?这很难说,但我认为最终,很多问题将归结为一种技术或另一种技术对代码库其余部分的处理效果如何。

    如果您已经使用组合和依赖注入来获得良好的效果,那么我认为继续使用该模式可能是一个好主意。

    如果您刚开始,还没有真正需要所有的火炮,或者还没有哲学上决定依赖注入是适合您的,您可以从mixin获得大量的里程,在运行时的复杂性上花费非常小的成本。

    我认为真正的答案将证明是高度情景化的。

    下面的DR

    问题1)我认为这是一个情境上有用的组合/DEP INJ的替代方案,但我认为除了简单性之外,它没有提供任何主要的收益。

    问题2)是的,它可以提高可测试性,主要是通过特性实现模拟模拟对象。


    我做过可以经验到使用混合和组成的组合。

    所以举例来说,使用组件将行为混合到一个特定的特性中。下面的示例显示了一个在类中使用多个DAO层特性的结构。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    trait ServiceXXX {
      def findAllByXXX(): Future[SomeClass]
    }

    trait ServiceYYY {
      def findAllByYYY(): Future[AnotherClass]
    }

    trait SomeTraitsComponent {
      val serviceXXX: ServiceXXX
      val serviceYYY: ServiceYYY
    }

    trait SomeTraitsUsingMixing {
      self: SomeTraitsComponent =>

      def getXXX() = Action.async {
        serviceXXX.findAllByXXX() map { results =>
          Ok(Json.toJson(results))
        }
      }

      def getYYY() = Actiona.async {
        serviceYYY.findAllByYYY() map {results =>
          Ok(Json.toJson(results))
        }
      }
    }

    之后,您可以声明一个具体的组件,并通过示例将其绑定到伴生对象:

    1
    2
    3
    4
    5
    6
    trait ConreteTraitsComponent extends SomeTraitsComponent {
      val serviceXXX = new ConcreteServiceXXX
      val serviceYYY = new ConcreteServiceYYY
    }

    object SomeTraitsUsingMixing extends ConreteTraitsComponent

    使用此模式,您可以轻松创建一个测试组件,并使用mock测试您的tait/类的具体行为:

    1
    2
    3
    4
    5
    6
    trait SomeTraitsComponentMock {
      val serviceXXX = mock[ServiceXXX]
      val serviceYYY = mock[ServiceYYY]
    }

    object SomeTraitsUsingMixingMock extends SomeTraitsComponentMock

    在您的规范中,您可以使用scalamock http://scalamock.org声明控制服务的结果。/