scala - Scala 值类的惯用方法

标签 scala idioms value-class

有谁知道是否有一种惯用的方式来命名 Scala 值类的内部值?假设我有一个产品 ID 的值类,它是否更好地定义为:

case class ProductId(productId:String) extends AnyVal

case class ProductId(underlying:String) extends AnyVal

case class ProductId(value:String) extends AnyVal

这只是一个偏好问题还是有惯用的指导方针?

最佳答案

我通常喜欢使用以下模式 -

final class ProductId(val underlying: String) extends AnyVal

这有以下好处 -

  • 您可以访问underlying随时为您提供值(value)
  • 通过使用 case class 来防止构造函数上的模式匹配 ,这有助于避免实际构造对象,因此运行时值保持为 String (或者无论你的潜在值(value)是什么)
  • 您可以使用伴生对象创建智能构造函数来验证其输入,并在构造值时提供更清晰的界面,以便用户不必使用 new .

下面是智能构造函数的示例 -

final class ProductId(val underlying: String) extends AnyVal

object ProductId {
  def apply(s: String): Result = {
    if (s.isEmpty) {
      new Failure("ProductId cannot be empty!")
    } else {
      new Success(new ProductId(s))
    }
  }

  sealed trait Result
  final case class Success(productId: ProductId) extends Result
  final case class Failure(message: String) extends Result
}

如果您想确保用户必须使用智能构造函数,请将您的值类的构造函数标记为私有(private) -

final class ProductId private (val underlying: String) extends AnyVal

如果您想确保不会意外分配 ProductId 的实例,你可以检查字节码 -

scala> :paste
class Test {
  def testProductId = new ProductId("foo")

  def testSmartCtor = ProductId("bar") match {
    case ProductId.Success(productId) => productId
    case ProductId.Failure(message) => throw new AssertionError(message)
  }
}

// Ctrl+D

:javap -c ProductId$

// Skipping to the apply() method

public ProductId$Result apply(java.lang.String);
  Code:
     0: aload_1
     1: invokevirtual #20                 // Method java/lang/String.isEmpty:()Z
     4: ifeq          19
     7: new           #22                 // class ProductId$Failure
    10: dup
    11: ldc           #24                 // String ProductId cannot be empty!
    13: invokespecial #27                 // Method ProductId$Failure."<init>":(Ljava/lang/String;)V
    16: goto          27
    19: new           #29                 // class ProductId$Success
    22: dup
    23: aload_1
    24: invokespecial #30                 // Method ProductId$Success."<init>":(Ljava/lang/String;)V
    27: areturn

没有new ProductId字节码中的引用,因此在运行时您的 ProductId将表示为 String .

请注意,如果您尝试将值类包装在使用泛型(例如 Option、Either)的类中,那么您的值将被装箱。您可以通过创建专门针对您的值类的简单案例类来避免这种情况。虽然 case 类将被实例化(因为您不能用另一个值类包装一个值类),但底层的 ProductId仍将表示为 String在运行时。

关于scala - Scala 值类的惯用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27380720/

相关文章:

c++ - 如何在 C++ 中实现生成器?

c++ - C++ 中严格类型定义的习语

Java:枚举和值类之间有什么区别(也在使用中)?

scala - 迁移到2.6 : lang is not being implicitly propagated out of the request

scala - 如何在 Scala 中定义存在的高级类型

scala - Scala for 循环内的赋值

scala - Int 类型与 scala.Int 类型

python - 在 Python 中填充字典的更惯用的方法

r - 为什么转置函数在 R 中将数字更改为字符?