2014-12-09 5 views
2

스칼라 값 클래스의 내부 값을 명명하는 관용적 인 방법이 있다면 아는 사람 있습니까? 제품 ID에 대한 가치 클래스가 있다고 가정하면 다음과 같이 정의하면됩니다.스칼라 값 클래스에 대한 관용적 접근법

case class ProductId(productId:String) extends AnyVal 

case class ProductId(underlying:String) extends AnyVal 

case class ProductId(value:String) extends AnyVal 

?

기본 설정 문제입니까 아니면 관용 지침이 있습니까?

+1

IMO,'underlying'는 그냥'value'보다 나은 방법입니다. –

+0

나는 어떤 표준도 모릅니다. 나는 '내부'를 짧고 분명하게 사용하는 경향이있다. – lmm

+1

저는 이것을하기위한 관용적 인 방법이 없다고 생각합니다. 왜냐하면 value 클래스는 컴파일러에게 좋은 트릭 일 뿐이 기 때문입니다. 그러나 @ om-nom-nom처럼 '근본적인 (underlying)'은 어떤 종류의 래퍼 (wrapper)를 만들어야하는 다른 경우들에서 매우 일반적입니다. – 4lex1v

답변

1

관용적 인 접근 방식은 이제 private val으로 만들 수 있습니다.

표준 라이브러리는 표준이되는 한 self을 선호합니다.

implicit final class ArrowAssoc[A](private val self: A) extends AnyVal 

문구 AnyVal의 scaladoc에서 "런타임 표현을 기본"와 컬렉션의 repr을 모두 회수하고, 또한 repr 있습니다.

class StringOps(override val repr: String) extends AnyVal with StringLike[String] 

in의 겉 핥기있다.

개인적으로 저는 특수 식별자 YMMV을 사용합니다. 이것은 다음과 같은 장점이있다

final class ProductId(val underlying: String) extends AnyVal 

- -

2

나는 일반적으로 다음과 같은 패턴을 사용하려면

  • 당신은 당신이 필요로 할 때마다 underlying 값에 액세스 할 수 있습니다
  • 방지 패턴이 생성자에 일치 이 아니며 case class을 사용하여 실제로 개체가 생성되지 않도록하여 런타임 값이로 유지됩니다.(또는 기본 값이 무엇이든간에)
  • 컴패니언 개체를 사용하여 스마트 생성자를 만들어 입력 유효성을 검사하고 사용자가 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 
} 

당신이 사용자 스마트 생성자를 사용해야하는지 확인하려면, 비공개로 당신의 가치 클래스의 생성자를 표시 -

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

실수로 ProductId의 인스턴스를 할당하지 않으려면 바이트 코드 -

을 확인할 수 있습니다. 런타임에 ProductIdString로 나타낼 수 있도록 363,210
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으로 표시됩니다.

+0

매우 상세한 necromancy :) 감사합니다 –