2017-08-12 3 views
9

Mein Anwendungsfall hat Fallklassen so etwas wieDSL in scala mit Fallklassen

case class Address(name:String,pincode:String){ 
    override def toString =name +"=" +pincode 
    } 

case class Department(name:String){ 
    override def toString =name 
} 

case class emp(address:Address,department:Department) 

Ich mag ein DSL wie below.Can niemand teilt die Links erstellen, wie ein DSL und Vorschläge erstellen das zu erreichen, unten.

emp.withAddress("abc","12222").withDepartment("HR") 

Update: Die tatsächlichen Anwendungsfall Klasse kann mehr Felder der Nähe von 20. Ich redudancy von Code vermeiden wollen

+2

Do not u Se Builder Muster in Scala. Sie sollten misstrauisch gegenüber jedem Hinweis sein, der darauf hinweist, dass Sie 'var' in scala verwenden. Es ist fast immer falsch, etwas zu tun. – Dima

+0

@Dima yeh true ..... aber es ist eine andere Möglichkeit, emp.withAddress ("abc", "12222") zu machen. WithDepartment ("HR") – coder25

+0

'def withAddress (Name: String, Pincode: String) = Kopie (Adresse = Adresse (Name, PIN-Code)) ' – Dima

Antwort

1

ich einen DSL mit Reflexion geschaffen, so dass wir jedes Feld, um es nicht hinzufügen müssen.

Disclamer: Dieses DSL ist extrem schwach typisiert und ich habe es nur zum Spaß gemacht. Ich glaube nicht, dass dies ein guter Ansatz in Scala ist.

scala> create an Employee where "homeAddress" is Address("a", "b") and "department" is Department("c") and that_s it 
res0: Employee = Employee(a=b,null,c) 

scala> create an Employee where "workAddress" is Address("w", "x") and "homeAddress" is Address("y", "z") and that_s it 
res1: Employee = Employee(y=z,w=x,null) 

scala> create a Customer where "address" is Address("a", "b") and "age" is 900 and that_s it 
res0: Customer = Customer(a=b,900) 

Das letzte Beispiel ist das Äquivalent des Schreibens:

create.a(Customer).where("address").is(Address("a", "b")).and("age").is(900).and(that_s).it 

Ein Weg von DSLs in Scala zu schreiben und Klammern vermeiden und der Punkt ist dieses Muster durch folgende:

object.method(parameter).method(parameter)... 

Hier ist die Quelle:

// DSL 

object create { 
    def an(t: Employee.type) = new ModelDSL(Employee(null, null, null)) 
    def a(t: Customer.type) = new ModelDSL(Customer(null, 0)) 
} 

object that_s 

class ModelDSL[T](model: T) { 
    def where(field: String): ValueDSL[ModelDSL2[T], Any] = new ValueDSL(value => { 
    val f = model.getClass.getDeclaredField(field) 
    f.setAccessible(true) 
    f.set(model, value) 
    new ModelDSL2[T](model) 
    }) 

    def and(t: that_s.type) = new { def it = model } 
} 

class ModelDSL2[T](model: T) { 
    def and(field: String) = new ModelDSL(model).where(field) 

    def and(t: that_s.type) = new { def it = model } 
} 

class ValueDSL[T, V](callback: V => T) { 
    def is(value: V): T = callback(value) 
} 

// Models 

case class Employee(homeAddress: Address, workAddress: Address, department: Department) 

case class Customer(address: Address, age: Int) 

case class Address(name: String, pincode: String) { 
    override def toString = name + "=" + pincode 
} 

case class Department(name: String) { 
    override def toString = name 
} 
3

Ich glaube wirklich nicht, dass Sie die Erbauer in Scala benötigen. Geben Sie Ihrer Fallklasse angemessene Standardwerte und verwenden Sie die Methode copy.

d.h .:

employee.copy(address = Address("abc","12222"), 
       department = Department("HR")) 

Sie auch einen unveränderlichen Builder verwenden:

case class EmployeeBuilder(address:Address = Address("", ""),department:Department = Department("")) { 
    def build = emp(address, department) 
    def withAddress(address: Address) = copy(address = address) 
    def withDepartment(department: Department) = copy(department = department) 
} 

object EmployeeBuilder { 
    def withAddress(address: Address) = EmployeeBuilder().copy(address = address) 
    def withDepartment(department: Department) = EmployeeBuilder().copy(department = department) 
} 
+0

Ich versuche, ein DSL ähnlich Builder-Muster – coder25

+0

Vielleicht ein wenig zu schaffen mehr Details zu deinem DSL? Ich bin mir sicher, dass es viel sinnvoller sein wird, es ohne das Builder-Muster zu erstellen. –

+0

Ich möchte eine DSL erstellen, die das Emp in der Frage erstellt .... ähnlich wie Generator, aber alle Referenzen zu einem DSL erstellen und lernen, wie man es erstellt – coder25

1

Sie könnten

object emp { 
    def builder = new Builder(None, None) 

    case class Builder(address: Option[Address], department: Option[Department]) { 
    def withDepartment(name:String) = { 
     val dept = Department(name) 
     this.copy(department = Some(dept)) 
    } 
    def withAddress(name:String, pincode:String) = { 
     val addr = Address(name, pincode) 
     this.copy(address = Some(addr)) 
    } 
    def build = (address, department) match { 
     case (Some(a), Some(d)) => new emp(a, d) 
     case (None, _) => throw new IllegalStateException("Address not provided") 
     case _ => throw new IllegalStateException("Department not provided") 
    } 
    } 
} 

tun und es als emp.builder.withAddress("abc","12222").withDepartment("HR").build() verwenden.

+0

danke ... aber angenommen, die Anzahl der Felder ist mehr als im Fall nahe 20, Erstellen eines Builders für für denselben Satz von Klasse wird in Duplikat – coder25

+0

Sie können wahrscheinlich ein Makro zu tun es für Sie, aber es ist bei weitem nicht trivial. –

+0

sah einen Beitrag http://callistaenterprise.se/blogg/teknik/2008/10/16/building-dsls-in-scala/ über DSL .cant sagen, ich kann es auf ähnliche Weise machen – coder25

1

Sie können optionale Felder benötigen, copy oder den Erbauer (genau), wenn Sie bereit sind, die Build haben immer nehmen die Argumente in einer bestimmten Reihenfolge:

case class emp(address:Address,department:Department, id: Long) 

object emp { 
    def withAddress(name: String, pincode: String): WithDepartment = 
    new WithDepartment(Address(name, pincode)) 

    final class WithDepartment(private val address: Address) 
    extends AnyVal { 
    def withDepartment(name: String): WithId = 
     new WithId(address, Department(name)) 
    } 

    final class WithId(address: Address, department: Department) { 
    def withId(id: Long): emp = emp(address, department, id) 
    } 
} 

emp.withAddress("abc","12222").withDepartment("HR").withId(1) 

Die Idee hier ist dass jeder Parameter emp seine eigene Klasse erhält, die eine Methode bereitstellt, um Sie zur nächsten Klasse zu bringen, bis die letzte Klasse Ihnen ein Objekt emp gibt. Es ist wie beim Curry, aber auf der Typenebene. Wie Sie sehen können, habe ich einen zusätzlichen Parameter hinzugefügt, nur als ein Beispiel, wie das Muster über die ersten beiden Parameter hinaus erweitert werden kann.

Das Schöne an diesem Ansatz ist, dass Sie, selbst wenn Sie den Build bereits abgeschlossen haben, mit dem bisherigen Typ den nächsten Schritt machen. Wenn Sie also bisher einen WithDepartment haben, wissen Sie, dass das nächste Argument, das Sie liefern müssen, ein Abteilungsname ist.

0

Wenn Sie vermeiden möchten, dass die Ursprungsklassen geändert werden, können Sie eine implizite Klasse verwenden, z.

implicit class EmpExtensions(emp: emp) { 
    def withAddress(name: String, pincode: String) { 
    //code omitted 
    } 

    // code omitted 
} 

dann wo müssen Sie diese Methoden

Verwandte Themen