在Scala中使用选项的惯用方法

时间:2011-01-27 22:01:37

标签: scala pattern-matching equals tostring options

我正在将一些Java代码转换为Scala,试图尽可能使代码成为惯用语。

所以,我现在有一些使用Options而不是可空值的代码,我想知道是不是scala'ish,或者我是不是错了。那么,你们可以批评下面的代码片段吗?

我专门寻找反馈的领域是:

  • 使用伴侣对象作为工厂,根据我们是否要传递选项或字符串提供2个选项:String构造函数是否正常,或者我们是否应该总是暴露它是一个选项的事实?
  • 前置条件的使用:是否有更好的方法来断言alpha3Code和name是必需的,并且必须为alpha2Code传递非null选项? (我使用Guava获取字符串utils,因为我在Scala API中没有找到任何内容)
  • 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()
}

3 个答案:

答案 0 :(得分:4)

我认为你应该只在工厂方法中公开Option[String]。例如,我作为您图书馆的用户,也会问自己问我应该使用哪种工厂方法。而且很可能我会使用Option。

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

def apply(name: String, alpha3Code: String, alpha2Code: Option[String] = None) = 
 new Language(name, alpha3Code, alpha2Code)

如果我再次作为你的库的用户,想要每次只传递字符串而不将其包装在Some中,我可以像这样编写自己的隐式转换:

implicit def anyToOption[T](t: T): Option[T] = Some(t)

甚至(如果我个人使用空值):

implicit def anyToOption[T](t: T): Option[T] = 
 if (t == null) None else Some(t)

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

答案 1 :(得分:4)

除非有充分的理由不这样做,否则你应该避免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)

前提条件很好。你可以这样写:

require(Option(alpha3Code) exists (_.nonEmpty))
require(Option(name) exists (_.nonEmpty))

但不一定是改进。

StringhashCode,所以我不明白你为什么要调用另一种方法来生成哈希码,而不是只调用alpha3Code.hashCode。我确实认为Scala API中有一些东西。不确定。

equals代码应采用canEqual方法,除非您创建课程sealedfinal。模式匹配几乎就是这样做的,尽管你可以这样写,因为存在一个提取器:

case Language(_, `alpha3Code`, _) => true

但你写它的方式几乎就是它通常写的方式。

答案 2 :(得分:-1)

我不喜欢选项 - 他们添加了一个间接级别,这在许多情况下是不必要的和令人困惑的。我更不喜欢空值,所以我理解通常使用选项是合理的。但是,您应该始终看看是否有更自然的方法来消除在界面中使用Option

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

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()
}

Guava docs