FP惯用的方式是什么:假设我有
trait Name
object Name{
def apply(name: String): Name = {
if (name.trim.isEmpty || name.trim.length < 3)
InvalidName
else
ValidName(name.trim)
}
}
case object InvalidName extends Name
case class ValidName(name:String) extends AnyVal with Name
现在我有一些辅助功能,例如
def split = name.splitAt(" ")
//some more functions
哪种方式更惯用:
将它们自己放置在case类中,但是这可以使case类包含一些逻辑,但是我可以这样做:
val n = ValidName("john smith")
val (first, last) = n.split
将它们放入专用对象中,然后方法看起来像
def split(n: ValidName) = n.name.splitAt(" ")
使用隐式类创建一个对象,该对象将接受Name并将调用方法
您怎么看?
答案 0 :(得分:2)
有与添加逻辑以没有重大问题一个case class
,特别是当它以不同格式只是提取数据。 (将数据成员添加到case class
时会出现问题。)
所以我会做选项1,不用担心!
作为对评论的回应,case class
实际上只是创建带有一堆有用的预实现方法的类的快捷方式。特别是,unapply
方法允许将case class
用于模式匹配,而equals
对两个实例的字段进行逐元素比较。
还有许多其他方法以不同的方式从case class
中提取数据。最明显的是toString
和copy
,但也有其他类似hashCode
,并从继承了所有东西Product
,例如productIterator
。
由于case class
已经具有以有用方式提取数据的方法,因此我不反对添加您的split
方法作为从case class
提取数据的另一种方法。
答案 1 :(得分:2)
更多惯用语:
case class Name private (name: String) {
lazy val first :: last :: Nil = name.split(" ").toList
}
object Name {
def fromString (name: String): Either[String, Name] = {
if (name.trim.isEmpty || name.trim.length < 3) Left("Invalid name")
else Right(new Name(name.trim))
}
}
或者也许是这样
case class Name (first: String, last: String) {
lazy val fullName = s"$first $last"
}
object Name {
def fromString (name: String): Either[String, Name] = {
if (name.trim.isEmpty || name.trim.length < 3) Left("Invalid name")
else {
val first :: last :: Nil = name.split(" ").toList
Right(new Name(first, last))
}
}
}
在scala中,通过使用Either
来表示失败案例比通过继承来表示失败案例更为常见。如果您有N
的实例,则无法在其上调用任何函数,则可能必须对其进行模式匹配。但是类似Either
的类型已经带有map
,fold
等功能,使其更易于使用。
拥有私有的构造函数有助于确保您只能创建有效的Name
,因为创建一个唯一的方法是通过fromString
方法。
请勿为此使用隐式。不需要,只会使代码混乱。隐含的含义不是真的。
答案 2 :(得分:1)
我认为这取决于上下文。在这种情况下,如果您使用的大多数方法只是对String
方法进行了细微调整,则可能需要考虑第四个选项:
case class Name(name: String)
implicit def NameToString(n: Name) = n.name
Name("Iron Man").split(" ") // Array(Iron, Man)