【问题标题】:Scala Objects and the rise of singletonsScala 对象和单例的兴起
【发布时间】:2012-11-18 17:59:38
【问题描述】:

一般风格问题。

随着我越来越擅长编写函数式代码,我的更多方法正在变成纯函数。我发现我的许多“类”(在代码容器的松散意义上)正在变得无状态。因此我将它们设为对象而不是类,因为不需要实例化它们。

现在在 Java 世界中,拥有一个充满“静态”方法的类似乎很奇怪,并且通常只用于“帮助”类,就像您在 Guava 和 Commons-* 等中看到的那样。

所以我的问题是,在 Scala 世界中,“对象”内部有很多逻辑,而不是“类”,这很正常,还是有另一个首选的成语。

【问题讨论】:

  • 等等,如果它们是无状态的,为什么它们甚至是对象而不是简单的函数?
  • 你不需要把函数放在里面,这是我的问题。我有一堆相关的功能,它们在一个对象中,只是作为一个容器。这是否被认为是 Scala 中的最佳实践。
  • 哦,好的。我误解了。谢谢你的澄清。
  • 在功能世界中,我们称这些容器模块。 Miranda 这样做了,F# 继续这样做。 “一般风格问题”可能是 Scala 的创建者希望他们的容器成为对象的原因。
  • 啊,模块是一流的值:2013.flatmap.no/spiewak.html

标签: scala functional-programming


【解决方案1】:

是的,我会说这是正常的。

对于我的大多数类,我创建了一个伴随对象来处理一些初始化/验证逻辑。例如,如果在构造函数中参数验证失败,而不是抛出异常,可以在伴随对象应用方法中返回 OptionEither

class X(val x: Int) {
  require(x >= 0)
}
// ==>
object X {
  def apply(x: Int): Option[X] =
    if (x < 0) None else Some(new X(x))
}
class X private (val x: Int)

在伴生对象中可以添加很多额外的逻辑,例如不可变对象的缓存。

如果不需要发送消息,对象也适用于在实例之间发送信号:

object X {
  def doSomething(s: String) = ???
}
case class C(s: String)

class A extends Actor {
  var calculateLater: String = ""
  def receive = {
    case X => X.doSomething(s)
    case C(s) => calculateLater = s
  }
}

对象的另一个用例是缩小元素的范围:

// traits with lots of members
trait A
trait B
trait C

trait Trait {

  def validate(s: String) = {
    import validator._
    // use logic of validator
  }

  private object validator extends A with B with C {
    // all members of A, B and C are visible here
  }
}

class Class extends Trait {
  // no unnecessary members and name conflicts here
}

【讨论】:

    【解决方案2】:

    正如您在标题中提到的,对象是单例类,而不是您在问题文本中提到的具有静态方法的类。

    还有一些东西使 scala 对象比 java-world 中的静态和单例更好,所以在 scala 中使用它们是非常“正常的”。

    一方面,与静态方法不同,对象方法是多态的,因此您可以轻松地将对象作为依赖项注入:

    scala> trait Quack {def quack="quack"}
    defined trait Quack
    
    scala> class Duck extends Quack
    defined class Duck
    
    scala> object Quacker extends Quack {override def quack="QUAACK"}
    defined module Quacker
    
    // MakeItQuack expects something implementing Quack
    scala> def MakeItQuack(q: Quack) = q.quack
    MakeItQuack: (q: Quack)java.lang.String
    
    // ...it can be a class
    scala> MakeItQuack(new Duck)
    res0: java.lang.String = quack
    
    // ...or it can be an object
    scala> MakeItQuack(Quacker)
    res1: java.lang.String = QUAACK
    

    这使得它们可以在没有紧密耦合和提升全局状态的情况下使用(这两个问题通常归因于静态方法和单例)。

    还有一个事实是,它们消除了所有使单例在 Java 中变得如此丑陋和单调的样板。在我看来,这是一个经常被忽视的点,也是使单例在 Java 中如此不受欢迎的部分原因,即使它们是无状态的并且不用作全局状态。

    此外,您必须在所有 java 单例中重复的样板文件赋予类两项职责:确保只有一个自身实例并执行它应该执行的任何操作。 scala 有一种声明式的方式来指定某物是单例,这一事实使类和程序员免于破坏单一责任原则。在 Scala 中,你知道一个对象是一个单例,你可以推断它的作用。

    【讨论】:

    • 我认为 scala 对象可以扩展任何东西都不能过分强调,这是将它与依赖注入习语一起使用的基础。谢谢你举个例子!
    • @ChristianSchlichtherle 谢谢,我只是希望我有一个更好的例子。我不太喜欢我给的做作的...
    【解决方案3】:

    您还可以使用包对象,例如看看这里的 scala.math 包对象 https://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_9_1_final/src//library/scala/math/package.scala

    【讨论】:

      猜你喜欢
      • 2020-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多