2016-02-23 6 views
0

Имея шаблон ниже строителя в Scala. Чтобы упростить это, я использую 3 экземпляра A, так что instance1 содержит только field1 и не имеет подключения к field2 или field3. Проблема в том, что везде в коде я должен использовать val s = A.instance1.field1.get; doSomething(s), где вызов get не является потенциально безопасным. Например, A.instance1.field2.get потерпит неудачу на None.get. Для того, чтобы охранять его, я должен соответствовать делу против варианта и иметь дело ни с кем случаями:Scala Builder шаблон с фантомными типами

object A { 
    val instance1 = new ABuilder().withField1("abc").build1 
    val instance2 = new ABuilder().withField1("abc").withField2("def").build2 
    val instance3 = new ABuilder().withField1("abc").withField3("def").build1 
} 

case class A(builder: ABuilder) { 
    val field1: Option[String] = builder.field1 
    val field2: Option[String] = builder.field2 
    val field3: Option[String] = builder.field3 
} 

class ABuilder { 
    var field1: Option[String] = None 
    var field2: Option[String] = None 
    var field3: Option[String] = None 
    def withField1(f: String): ABuilder = { 
    this.field1 = Some(f) 
    this 
    } 
    def withField2(f: String): ABuilder = { 
    this.field2 = Some(f) 
    this 
    } 
    def withField3(f: String): ABuilder = { 
    this.field3 = Some(f) 
    this 
    } 
    def build1: A = { 
    require(field1.isDefined, "field 1 must not be None") 
    A(this) 
    } 
    def build2: A = { 
    require(field1.isDefined, "field 1 must not be None") 
    require(field2.isDefined, "field 2 must not be None") 
    A(this) 
    } 
} 

Другим решением было бы использовать параметризованные типы, называемые также фантомные тип. Я нашел очень мало хороших учебников по этому вопросу и не смог найти ни в одном из них, как реализовать шаблон безопасного шаблона типа в Scala с фантомными типами и фактическими данными (или состоянием) - все примеры описывают только методы.

Как я могу использовать типы фантомов в моем примере, чтобы избежать исключений во время выполнения None и получить только прекрасные исключения типа-несоответствия? Я пытаюсь параметризовать все упомянутые классы и методы и использовать закрытые черты, но пока не добился успеха.

+0

Может FIELD2 или field3 имеют значения по умолчанию? –

+0

Нет, я пытаюсь не использовать '.getOrElse', но использовать два разных подтипа строителей. Этот пример относится к типам фантомов или параметризованным типам и их границам. –

+0

Для этого вам не нужны фантомные типы. Просто заставьте ваш строитель принять требуемый параметр в конструкторе: 'new ABuilder (" foo ") .FFF2 (" bar ")' – Dima

ответ

1

Если вы действительно хотите использовать фантомные типы вы могли бы сделать

object PhantomExample { 
    sealed trait BaseA 
    class BaseAWith1 extends BaseA 
    final class BaseAWith12 extends BaseAWith1 

    object A { 
    val instance1 = new ABuilder().withField1("abc").build1 
    val instance2 = new ABuilder().withField1("abc").withField2("def").build2 
    } 

    case class A[AType <: BaseA](builder: ABuilder) { 
    def field1[T >: AType <: BaseAWith1] = builder.field1.get 
    def field2[T >: AType <: BaseAWith12] = builder.field2.get 
    } 

    class ABuilder { 
    var field1: Option[String] = None 
    var field2: Option[String] = None 
    def withField1(f: String): ABuilder = { 
     this.field1 = Some(f) 
     this 
    } 
    def withField2(f: String): ABuilder = { 
     this.field2 = Some(f) 
     this 
    } 
    def build1: A[BaseAWith1] = { 
     require(field1.isDefined, "field 1 must not be None") 
     A(this) 
    } 
    def build2: A[BaseAWith12] = { 
     require(field1.isDefined, "field 1 must not be None") 
     require(field2.isDefined, "field 2 must not be None") 
     A(this) 
    } 
    } 

    val x = A.instance1.field1      //> x : String = abc 
    val x2 = A.instance2.field1      //> x2 : String = abc 
    val x3 = A.instance2.field2      //> x3 : String = def 

    // This gives compilation error 
    //val x2 = A.instance1.field2 
} 

Однако я не рекомендую использовать этот вид кода в производстве. Я думаю, что это выглядит уродливо, ошибка компиляции кажется загадочной, и IMHO - не лучшее решение. Подумайте об этом, если ваши экземпляры настолько разные, может быть, они даже не экземпляры одного и того же конкретного класса?

trait BaseA { 
    def field1 
} 
class A1 extends BaseA { } 
class A2 extends BaseA { ... def field2 = ... } 
+0

Эй, я действительно хочу принять ваш ответ, но у меня есть эта ошибка: «Нижняя граница не соответствует верхней границе» в точке 'def field1 [T>: AType <: BaseAWith1]' подпись (то же самое для 'def field2'). Не могли бы вы помочь разрешить это? Скомпилирован ли он для вас? –

+0

Да, он компилируется. Можете ли вы создать свой код? –

+0

Мой код точно такой же, как у вас, я скопировал его, 'sbt 0.13.8' и' scala 2.10.5'. Не знаете, как это сделать. –

0

Я не уверен, что это то, что вы хотите, но я думаю, вы можете взять его в качестве базы.

Сначала Класс:

case class A(field1: String = "", 
       field2: String = "", 
       field3: String = "") 

Класс случай имеет значения пустых строк по умолчанию. Это позволяет нам создать любой объект A с любым значением поля, заданным без учета значений None.

Например:

val b2 = A("abc", "def") 
> b2: A = A(abc,def,) 

val b1 = A("abc") 
> b1: A = A(abc,,) 

val notValidB = A(field2 = "xyz") 
> notValidB: A = A(,xyz,) 

Как вы можете видеть, b2 и b1 являются действительными объектов и notValidB не действует, так как ваш объект требует field1.

Вы можете создать другую функцию, которая использует сопоставление шаблонов для проверки ваших объектов A и последующего действия с определенными действиями.

def determineAObj(obj: A): Unit = obj match { 
    case A(f1, f2, _) if !f1.isEmpty && !f2.isEmpty => println("Is build2") 
    case A(f1, _, _) if !f1.isEmpty => println("Is build1") 
    case _ => println("This object doesn't match (build1 | build2)") 
} 

А затем запустить:

determineAObj(b1) 
> "Is build1" 

determineAObj(b2) 
> "Is build2" 

determineAObj(notValidB) 
> "This object doesn't match (build1 | build2)"