Scala 使用选项的惯用方式';在斯卡拉

Scala 使用选项的惯用方式';在斯卡拉,scala,pattern-matching,equals,tostring,options,Scala,Pattern Matching,Equals,Tostring,Options,我正在将一些Java代码转换为Scala,试图使代码尽可能地道 所以,我现在有一些代码使用选项而不是可为空的值,我想知道这是scala'ish,还是我错了。所以,你们能批评一下下面的代码片段吗 我特别希望得到反馈的领域包括: 将伴生对象用作工厂,根据传递选项还是字符串给出两个选项:字符串构造函数好吗,还是应该始终公开它是选项的事实 先决条件的使用:是否有更好的方法来断言alpha3Code和name是强制性的,并且必须为alpha2Code传递非空选项?(我正在使用字符串utils,因为我在s

我正在将一些Java代码转换为Scala,试图使代码尽可能地道

所以,我现在有一些代码使用选项而不是可为空的值,我想知道这是scala'ish,还是我错了。所以,你们能批评一下下面的代码片段吗

我特别希望得到反馈的领域包括:

  • 将伴生对象用作工厂,根据传递选项还是字符串给出两个选项:字符串构造函数好吗,还是应该始终公开它是选项的事实
  • 先决条件的使用:是否有更好的方法来断言alpha3Code和name是强制性的,并且必须为alpha2Code传递非空选项?(我正在使用字符串utils,因为我在scalaapi中没有找到任何东西)
  • hashCode、equals和toString的实现。equals和toString再次委托给Guava,而equals使用模式匹配。有没有一种更具鳞片的方法
  • 我知道我可以使用Case类,这将创建默认实现,但我最感兴趣的是学习如何在Case类无法使用的情况下实现这些
非常感谢

package com.sirika.openplacesearch.api.language

import com.google.common.base.Objects
import com.google.common.base.Strings

object Language {
    def apply(name : String, alpha3Code : String, alpha2Code : Option[String]) = new Language(name, alpha3Code, alpha2Code)
    def apply(name : String, alpha3Code : String, alpha2Code : String = null) = new Language(name, alpha3Code, Option(alpha2Code))
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
}


class Language(val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
    require(!Strings.isNullOrEmpty(alpha3Code))
    require(!Strings.isNullOrEmpty(name))
    require(alpha2Code != null)

    override def hashCode(): Int = Objects.hashCode(alpha3Code)

            override def equals(other: Any): Boolean = other match {
        case that: Language => this.alpha3Code == that.alpha3Code
        case _ => false
    }

    override def toString() : String = Objects.toStringHelper(this)
        .add("name", name)    
        .add("alpha3", alpha3Code)
        .add("alpha2", alpha2Code)
        .toString()
}

我认为您应该只在工厂方法中公开
选项[String]
。例如,作为您库的用户,我也会问自己应该使用哪种工厂方法。我很可能会使用这个选项

Scala为我们提供了足够的工具,让我们的生活更轻松。例如,您可以对以下选项使用默认值:

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
 new Language(name, alpha3Code, alpha2Code)
implicit def anyToOption[T](t: T): Option[T] = Some(t)
require(Option(alpha3Code) exists (_.nonEmpty))
require(Option(name) exists (_.nonEmpty))
package com.sirika.openplacesearch.api.language

import com.google.common.base.Strings
import com.google.common.base.Objects

object Language {
    def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, alpha2Code)
    def apply(name : String, alpha3Code : String ) = new Language(name, alpha3Code)
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
}


class Language private (val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
    def this(name:String,alpha3Code: String ,alpha2Code:String) = this(name,alpha3Code,Option(alpha2Code))
    def this(name:String,alpha3Code: String) = this(name,alpha3Code,None)

    require(!Strings.isNullOrEmpty(alpha3Code))
    require(!Strings.isNullOrEmpty(name))

    override def hashCode  = alpha3Code.hashCode

    override def equals(other: Any) = other match {
        case that: Language => this.alpha3Code == that.alpha3Code
        case _ => false
    }

    override def toString = MoreObjects.toStringHelper(this)
        .add("name", name)    
        .add("alpha3", alpha3Code)
        .add("alpha2", alpha2Code)
        .toString()
}
如果我还是您库的用户,每次只传递字符串而不将其包装在
Some
中,我可以编写自己的隐式转换,如下所示:

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
 new Language(name, alpha3Code, alpha2Code)
implicit def anyToOption[T](t: T): Option[T] = Some(t)
require(Option(alpha3Code) exists (_.nonEmpty))
require(Option(name) exists (_.nonEmpty))
package com.sirika.openplacesearch.api.language

import com.google.common.base.Strings
import com.google.common.base.Objects

object Language {
    def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, alpha2Code)
    def apply(name : String, alpha3Code : String ) = new Language(name, alpha3Code)
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
}


class Language private (val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
    def this(name:String,alpha3Code: String ,alpha2Code:String) = this(name,alpha3Code,Option(alpha2Code))
    def this(name:String,alpha3Code: String) = this(name,alpha3Code,None)

    require(!Strings.isNullOrEmpty(alpha3Code))
    require(!Strings.isNullOrEmpty(name))

    override def hashCode  = alpha3Code.hashCode

    override def equals(other: Any) = other match {
        case that: Language => this.alpha3Code == that.alpha3Code
        case _ => false
    }

    override def toString = MoreObjects.toStringHelper(this)
        .add("name", name)    
        .add("alpha3", alpha3Code)
        .add("alpha2", alpha2Code)
        .toString()
}
甚至(如果我个人使用空值):


但我相信,如果您强制执行该选项,它将使您的API更加可靠和清晰。

您应该避免
null
,除非有很好的理由不这样做。事实上,你可以这样写:

def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, Option(alpha2Code))
def apply(name : String, alpha3Code : String) = new Language(name, alpha3Code, None)
前提条件很好。你可以这样写:

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
 new Language(name, alpha3Code, alpha2Code)
implicit def anyToOption[T](t: T): Option[T] = Some(t)
require(Option(alpha3Code) exists (_.nonEmpty))
require(Option(name) exists (_.nonEmpty))
package com.sirika.openplacesearch.api.language

import com.google.common.base.Strings
import com.google.common.base.Objects

object Language {
    def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, alpha2Code)
    def apply(name : String, alpha3Code : String ) = new Language(name, alpha3Code)
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
}


class Language private (val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
    def this(name:String,alpha3Code: String ,alpha2Code:String) = this(name,alpha3Code,Option(alpha2Code))
    def this(name:String,alpha3Code: String) = this(name,alpha3Code,None)

    require(!Strings.isNullOrEmpty(alpha3Code))
    require(!Strings.isNullOrEmpty(name))

    override def hashCode  = alpha3Code.hashCode

    override def equals(other: Any) = other match {
        case that: Language => this.alpha3Code == that.alpha3Code
        case _ => false
    }

    override def toString = MoreObjects.toStringHelper(this)
        .add("name", name)    
        .add("alpha3", alpha3Code)
        .add("alpha2", alpha2Code)
        .toString()
}
不过,这并不一定是一种进步

String
具有
hashCode
,因此我不明白为什么您要调用另一个方法来生成哈希代码,而不是只调用
alpha3Code.hashCode
。不过,我确实认为Scala API中有一些东西。不确定

equals
代码应该有一个
canEqual
方法,除非您将类
密封
最终
。模式匹配基本上就是这样做的,不过如果有提取器,您可以这样编写:

case Language(_, `alpha3Code`, _) => true
但是你写它的方式和通常写它的方式差不多。

我不喜欢选项——它们增加了一种间接层次,在很多情况下都是不必要的和令人困惑的。我更不喜欢空值,所以我知道使用选项通常是合理的。但是,您应该始终了解是否有更自然的方法来消除在界面中使用
选项

默认参数或单独重载通常是更好的选择。所以我会像这样重写你的代码:

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
 new Language(name, alpha3Code, alpha2Code)
implicit def anyToOption[T](t: T): Option[T] = Some(t)
require(Option(alpha3Code) exists (_.nonEmpty))
require(Option(name) exists (_.nonEmpty))
package com.sirika.openplacesearch.api.language

import com.google.common.base.Strings
import com.google.common.base.Objects

object Language {
    def apply(name : String, alpha3Code : String, alpha2Code : String) = new Language(name, alpha3Code, alpha2Code)
    def apply(name : String, alpha3Code : String ) = new Language(name, alpha3Code)
    def unapply(l : Language) = Some(l.name, l.alpha3Code, l.alpha2Code )
}


class Language private (val name : String, val alpha3Code : String, val alpha2Code : Option[String]) {
    def this(name:String,alpha3Code: String ,alpha2Code:String) = this(name,alpha3Code,Option(alpha2Code))
    def this(name:String,alpha3Code: String) = this(name,alpha3Code,None)

    require(!Strings.isNullOrEmpty(alpha3Code))
    require(!Strings.isNullOrEmpty(name))

    override def hashCode  = alpha3Code.hashCode

    override def equals(other: Any) = other match {
        case that: Language => this.alpha3Code == that.alpha3Code
        case _ => false
    }

    override def toString = MoreObjects.toStringHelper(this)
        .add("name", name)    
        .add("alpha3", alpha3Code)
        .add("alpha2", alpha2Code)
        .toString()
}

实际上,有一种更简单的方法可以将对象包装在
选项中,这样
空值将变为
无值
选项(t)
(而不是
Some(t)
)使用选项的“诀窍”是只使用选项,并强制消费者也这样做;-)当然,这在处理Java时并不总是实用的(哎呀!)。欢迎来到SO。我不认为require(alpha2Code!=null)会失败,因为alpha2Code是一个选项。其他答案似乎强调了一个事实,即我应该强制消费者使用选项构造函数。你同意这个说法吗?关于hashCode,在这种情况下,我确实可以直接使用alpha3Code.hashCode,我甚至没有想到它,因为我习惯于使用guava语法,它允许添加几个键:Objects.hashCode(alpha3Code,name)和canEqual方法的优点。我没有意识到这一点。对于其他不知道canEqual的人,你也可以参考:字母3之间的代码是故意的吗?他们的意思是“值应该与这个.alpha3Code匹配”吗?我找不到关于这个的更多信息,所以我非常希望有一个链接。在模式匹配中,反勾号对编译器来说意味着:“假装这个值是大写的”。