将方法放在 case class 中与其伴随对象相比有什么好处?

What is the benefit of putting a method inside a case class compared to in its companion object?

假设我有这样一个案例class:

final case class Foo(a : String)

并且我想要一个对 Foo 进行操作的方法。那么我可以这样做:

final case class Foo(a : String){

   def m : Int = a.toInt

}

或者我可以


object Foo{

   def m (f : Foo) : Int = f.a.toInt

}

哪个最好或者每个的优点和缺点是什么?

至少在概念上,class 上的方法通常是转换实例的操作。

您可以这样描绘对象的生命周期:

A -----> MyType -----> Mytype ----> B

  \___/         \___/        \___/  
    |             |            |
construct    transform   eliminate/consume/fold

习惯上将构造函数放在伴生对象中,将转换放在 class 定义中。

在 Scala 3 中,我发现以下模式很有用:

  • 将核心/小方法放在class本身
  • 将更大/更不常用的方法放在扩展部分。

其实你问的是x.m()m(x)有什么区别。不同之处在于方法是如何解决的。可以有不同的同名方法 m.

如果 x.m() 方法在运行时动态解析(覆盖、后期绑定、子类型多态性)

class Foo {
  def m(): Unit = println("Foo")
}
class Bar extends Foo {
  override def m(): Unit = println("Bar")
}

val x: Foo = new Bar
x.m() // Bar

如果 m(x) 方法在编译时静态解析(重载、早期绑定、临时多态性)

class Foo
class Bar extends Foo

def m(x: Foo): Unit = println("Foo")
def m(x: Bar): Unit = println("Bar")

val x: Foo = new Bar
m(x) // Foo

class其中之一 FooBar 可以是一个案例 class。

后一种方法(“静态”)也可以使用类型 classes 而不是重载

来实现
trait DoM[T] {
  def m(t: T): Unit
}
def m[T](t: T)(implicit dm: DoM[T]): Unit = dm.m(t)

class Foo 
class Bar extends Foo

implicit val fooDoesM: DoM[Foo] = _ => println("Foo")
implicit val barDoesM: DoM[Bar] = _ => println("Bar")

val x: Foo = new Bar
m(x) // Foo

这两个适用于非常不同的用例,因此它们之间不应该有任何比较。

任何可以被认为是对象实例的“行为”并且是对象固有的东西最好用 class / case class 方法来实现。

这一点也与共享行为和继承有关。使用伴随对象管理继承层次结构中的行为覆盖将非常麻烦。对于 sealed.

以外的任何事情,这几乎是不可能的(而且非常难看)

它还可以让您轻松重构代码,以防您将来需要将此 case class 抽象为 trait

任何感觉像是对象实例之上的实用程序的东西都可以转到伴随对象、工厂管理器对象或某些实用程序对象。