将方法放在 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其中之一 Foo
、Bar
可以是一个案例 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
。
任何感觉像是对象实例之上的实用程序的东西都可以转到伴随对象、工厂管理器对象或某些实用程序对象。
假设我有这样一个案例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其中之一 Foo
、Bar
可以是一个案例 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
。
任何感觉像是对象实例之上的实用程序的东西都可以转到伴随对象、工厂管理器对象或某些实用程序对象。