2015-03-02 14 views
5

diese einfache Klasse vor:Wie kann ich eine anonyme Instanz eines generischen Typs erstellen?

class MyClass(p: (String, String) *) { 
    val params: Map[String, String] = p.toMap 
} 

Und eine Klasse, die es erweitert:

class SomeOtherClass(p: (String, String) *) extends MyClass(p: _*) 

Ich möchte entfernen Sie den Konstruktor von MyClass zu vermeiden Tragen um den Initialisierungsparameter in allem, was sie erstreckt (potenziell viele Arten). Stattdessen würde ich bevorzugen, dass die Companion-Objekte der Typen, die MyClass erweitern, eine Art Builder-Methode erben.

class MyClass { 
    val param = Map.empty[String, String] // some member with a default value 
} 

trait MyClassBuilder[A <: MyClass] { 
    def apply(p: (String, String) *): A = ??? 
} 

Grundsätzlich apply sollte so etwas tun:

new A { 
    override val param = p.toMap 
} 

Offensichtlich ist die obige Code nicht funktionieren kann. Die Idee ist, dass ein Untertyp wie folgt verwendet werden würde:

class SomeOtherClass extends MyClass 
object SomeOtherClass extends MyClassBuilder[SomeOtherClass] { 
    // object specific things.. 
} 

Dann SomeOtherClass würde verlassen sich auf die ererbten apply Methode für Instanzen von sich selbst zu schaffen. Es scheint so etwas mit Reflektion oder Makros möglich zu sein, aber ich habe wirklich keine Ahnung. Um klar zu sein, möchte ich, dass Client-Code wie SomeOtherClass keinen Konstruktor benötigt, weshalb ich das Erstellen von anonymen Klassen für einen generischen Typ untersuchen möchte. Es könnte nicht SomeOtherClass sein, das generiert wird, es könnte alles sein, was MyClass erweitert.

+0

Ich wette, ein Makro ist die Weg zu gehen hier, die würde die Boilerplate produzieren, die Sie versuchen zu vermeiden –

Antwort

2

Dies kann Ihnen helfen:

import scala.reflect._ 
abstract class MyClassBuilder[A <: MyClass: ClassTag] { 
    def apply() = classTag[A].runtimeClass.newInstance.asInstanceOf[A] 
} 

Sie können finde, dass ich (String, String) * hier nicht passiert habe, da sie zur Instanziierung redundant sind. Wie auch immer, Sie haben hier Zugriff auf die gesamte A Instanz, so dass Sie nach der Instanziierung zumindest die param ändern können. Es ist nicht genau das, was Sie wollen - wie Sie nach einer Möglichkeit suchen, in Laufzeit SomeOtherClass zu erweitern.Allerdings Laufzeit Schaffung neuen Typs in scala ist unmöglich, aber man kann es als Prototyp denkt - nur SomeOtherClass Beispiel nehmen und mutiert einmal in apply():

import scala.reflect._ 
object Ctx { 
    class MyClass { 
    private[Ctx] var _param = Map.empty[String, String] 
    def param = _param 
    } 

    abstract class MyClassBuilder[A <: MyClass: ClassTag] { 
    def apply(p: (String, String) *) = { 
     val i = classTag[A].runtimeClass.newInstance.asInstanceOf[A] 
     i._param = Map(p: _*) 
     i 
    } 
    } 
} 

scala> class MySpecialClass extends Ctx.MyClass 
defined class MySpecialClass 

scala> object MySpecialBuilder extends Ctx.MyClassBuilder[MySpecialClass] 
defined module MySpecialBuilder 

scala> MySpecialBuilder("A" -> "b") 
res12: MySpecialClass = [email protected] 

scala> res12.param 
res13: scala.collection.immutable.Map[String,String] = Map(A -> b) 

Ansonsten haben Sie mit Kompilierung-Reflexion beschäftigen.

Eine interessante Alternative ist extensible records (Shapeless2) - sie praktisch können Sie für Stück neue Art Stück bauen, aber Sie werden es mit HList statt Klasse zu tun haben:

import shapeless._ ; import syntax.singleton._ ; import record._ 
import shapeless.ops.record.Updater 
import scala.reflect.runtime.universe._ 

val param = Witness("param") 
val someFunW = Witness("someFun") 

//defining classess (instead of "class MyClass") 

type Param = Map[String, String] with KeyTag[param.T, Map[String, String]] 
type SomeFun = (String => String) with KeyTag[someFunW.T, (String => String)] 
type MyClass = Param :: HNil 
type MySpecialClass = SomeFun :: MyClass 

def someFun(s: String) = s + "A" 

//defining default values (instead of "val param = ...") 

def newMyClass[T <: HList : TypeTag]: T = ( 
    if (typeTag[T] == typeTag[MyClass]) 
     (param ->> Map.empty[String, String]) :: HNil 
    else if (typeTag[T] == typeTag[MySpecialClass]) 
     (someFunW ->> someFun _) :: newMyClass[MyClass] 
    else HNil 
).asInstanceOf[T] 

//Defining builder 
def buildWithParam[T <: HList: TypeTag](p: Map[String, String])(implicit ev: Updater[T, Param]) 
    = newMyClass[T] + ("param" ->> p) 

scala> buildWithParam[MySpecialClass](Map("a" -> "v")) 
res6: ... = <function1> :: Map(a -> v) :: HNil 

scala> res6("someFun").apply("a") 
res7: String = aA 

scala> buildWithParam[MyClass](Map("a" -> "v")) 
res8: ... = Map(a -> v) :: HNil 
+0

* "Runtime Erstellung eines neuen Typs in Scala ist unmöglich" * - Das ist so ziemlich der Satz, den ich brauchte. Das ist sehr hilfreich und gibt mir viele Ideen, mit denen ich arbeiten kann. –

+0

Ich bin besorgt über die Gleichheitstests für Typ-Tags hier - 'typeTag [T] == typeTag [MyClass]'. Es ist nicht schwer, zwei verschiedene Typ-Tags für denselben zugrunde liegenden Typ zu erhalten. –

0

Mit einem Typ als folgendes Merkmal;

trait MyType { 
    def params: Map[String, String] 
} 

Eine Instanz kann durch das Begleitobjekt erstellt werden:

object MyType { 
    def apply(pairs: (String, String)*): MyType = new MyType { 
    val params = pairs.toMap 
    } 
} 
//allows: MyType("a" -> "b", "c" -> "d") 
+0

Ich denke, er fragt, wie man das mit einem generischen tut –

+0

Tatsächlich versuche ich zu vermeiden, die Boilerplate dieser 'apply' Methode für Untertypen zu tragen . –

-1

Sie können in der Unterklasse wie diese einen nicht-Parameter Konstruktor hinzu:

class SomeOtherClass(p: (String, String)*) extends MyClass(p: _*) { 
    def this() = this(somedefaultvalueofp) 
} 

und dann Begleiter-Objekt verwenden mit gelten, um den Wert von außen anzuwenden:

object SomeOtherClass { 
    def apply(p: (String, String)*): SomeOtherClass = new SomeOtherClass(p) 
} 

********** bearbeitet ****************

Ich verstehe jetzt die Anforderung. Sie könnten also die Nicht-Parameter-Konstruktor auf Ihrem MyClass hinzufügen:

class MyClass(p: (String, String)*) { 
    def this() = this(defaultvalueofp) 
} 

Und eine Klasse Builder erstellen, wie Sie erwähnt:

class MyClassBuilder[T <: MyClass] { 
    def apply(p: (String, String)*): T = new T(p) 
} 
Verwandte Themen