2010-11-30 11 views
38

Ich konnte die Antwort darauf in keiner anderen Frage finden. Angenommen, ich habe eine abstrakte Oberklasse Abstract0 mit zwei Unterklassen, Concrete1 und Concrete1. Ich möchte in der Lage sein, in Abstract0 so etwas wieWie benutze Scala diese Typisierung, abstrakte Typen usw., um einen Selbsttyp zu implementieren?

def setOption(...): Self = {...} 

zu definieren, wobei Self der konkrete Untertyp wäre. Dies würde es ermöglichen Anrufe Verkettungs wie diese setOption:

val obj = new Concrete1.setOption(...).setOption(...) 

und noch Concrete1 als abgeleitete Art von Objekt zu bekommen.

Was ich will nicht, dies zu definieren:

abstract class Abstract0[T <: Abstract0[T]] 

, weil es macht es schwieriger für Kunden diese Art zu behandeln. Ich habe versucht, verschiedene Möglichkeiten, einschließlich einer abstrakten Art:

abstract class Abstract0 { 
    type Self <: Abstract0 
} 

class Concrete1 extends Abstract0 { 
    type Self = Concrete1 
} 

aber dann ist es unmöglich, setOption zu implementieren, weil this in Abstract0 nicht Art Selbst haben. Und die Verwendung von this: Self => funktioniert auch nicht in Abstract0.

Welche Lösungen gibt es für dieses Problem?

+0

Eine Möglichkeit ist zum Beispiel 'protected def self = this.asInstanceOf [Self]' und dann 'def setOption (...) = {... zu definieren; Selbst} ', aber das sieht ein bisschen hässlich aus ... –

Antwort

50

Dies ist, was this.type ist für:

scala> abstract class Abstract0 { 
    | def setOption(j: Int): this.type 
    | } 
defined class Abstract0 

scala> class Concrete0 extends Abstract0 { 
    | var i: Int = 0 
    | def setOption(j: Int) = {i = j; this} 
    | } 
defined class Concrete0 

scala> (new Concrete0).setOption(1).setOption(1) 
res72: Concrete0 = [email protected] 

Wie Sie sehen können setOption den tatsächlichen Typ zurückgibt verwendet, nicht Abstract0. Wenn Concrete0 setOtherOption hatte dann würde (new Concrete0).setOption(1).setOtherOption(...) arbeiten

UPDATE: Um JPP des Followup Frage im Kommentar zu beantworten (wie neue Instanzen zurückkehren: Der General in der Frage beschriebene Ansatz ist die richtige (mit abstrakten Typen) jedoch die. Schaffung der neuen Instanzen muss für jede Unterklasse explizit sein

ein Ansatz ist:..

abstract class Abstract0 { 
    type Self <: Abstract0 

    var i = 0 

    def copy(i: Int) : Self 

    def setOption(j: Int): Self = copy(j) 
} 

class Concrete0(i: Int) extends Abstract0 { 
    type Self = Concrete0 
    def copy(i: Int) = new Concrete0(i) 
} 

ein weiterer ist die Erbauer in Scala Sammlung Bibliothek verwendet folgen das heißt, setOption erhält einen impliziten Builder Parameter Dies hat th Die Vorteile, dass das Erstellen der neuen Instanz mit mehr Methoden als nur "Kopieren" erfolgen kann und komplexe Builds möglich sind. Z.B. Eine setSpecialOption kann angeben, dass die zurückgegebene Instanz SpecialConcrete sein muss.

Hier ist eine Darstellung der Lösung:

trait Abstract0Builder[To] { 
    def setOption(j: Int) 
    def result: To 
} 

trait CanBuildAbstract0[From, To] { 
    def apply(from: From): Abstract0Builder[To] 
} 


abstract class Abstract0 { 
    type Self <: Abstract0 

    def self = this.asInstanceOf[Self] 

    def setOption[To <: Abstract0](j: Int)(implicit cbf: CanBuildAbstract0[Self, To]): To = { 
    val builder = cbf(self) 
    builder.setOption(j) 
    builder.result 
    } 

} 

class Concrete0(i: Int) extends Abstract0 { 
    type Self = Concrete0 
} 

object Concrete0 { 
    implicit def cbf = new CanBuildAbstract0[Concrete0, Concrete0] { 
     def apply(from: Concrete0) = new Abstract0Builder[Concrete0] { 
      var i = 0 
      def setOption(j: Int) = i = j 
      def result = new Concrete0(i) 
     } 
    } 
} 

object Main { 
    def main(args: Array[String]) { 
    val c = new Concrete0(0).setOption(1) 
    println("c is " + c.getClass) 
    } 
} 

UPDATE 2: zu JPP zweiten Kommentar beantworten. Bei mehreren Verschachtelungsebenen, einen Typparameter Mitglied anstelle von Typ verwenden und Abstract0 in einen Zug machen:

trait Abstract0[+Self <: Abstract0[_]] { 
    // ... 
} 

class Concrete0 extends Abstract0[Concrete0] { 
    // .... 
} 

class RefinedConcrete0 extends Concrete0 with Abstract0[RefinedConcrete0] { 
// .... 
} 
+0

Sehr nette Sprachfunktion!Ich kann mich nicht daran erinnern, es im Programm "Programmierung in Scala" gesehen zu haben. Aber jetzt eine Nachfolgefrage: das funktioniert nur bei der Rückgabe. Was ist, wenn ich ein anderes Objekt des gleichen konkreten Typs zurückgeben möchte, zum Beispiel für eine Klon-Methode? (Ich weiß, dass ich kostenlos klonen kann, als 'Kopie' in Fallklassen, aber ich würde mich trotzdem für die Frage interessieren.) –

+0

Danke für die nachfolgende Erklärung. Eine letzte Frage. Wenn 'Concrete0' selbst eine Unterklasse' RefinedConcrete0' hat, kann ich den konkreten Typ Self, der bereits in 'Concrete0' angegeben wurde, nicht überschreiben. Ich nehme an, das Builder-Muster ist dann die bevorzugte Option? Oder kann sonst noch etwas verwendet werden? Ich nehme an, dass Punkt 33 von Effektiverem C++ auch hier angewendet werden kann: Machen Sie Nicht-Leaf-Klassen Abstract ... –

+0

Was wäre der Unterschied zwischen 'this.type' und' Abstract0'? Ich sehe keine. Das funktioniert für mich, weil es sowieso den Regeln der Varianz folgt. – Debilski

4

Dies ist der genaue Anwendungsfall von this.type. Es wäre wie:

def setOption(...): this.type = { 
    // Do stuff ... 
    this 
}