Frage

Ein Selbsttyp für ein Merkmal A:

trait B
trait A { this: B => }

sagt, dass "A kann nicht in eine konkrete Klasse gemischt werden, die sich nicht erstreckt B".

Andererseits Folgendes: Folgendes:

trait B
trait A extends B

sagt, dass "Jeder (konkrete oder abstrakte) Klassenmischung in A wird auch in B mischen ".

Bedeuten diese beiden Aussagen nicht dasselbe? Der Selbsttyp scheint nur dazu zu dienen, die Möglichkeit eines einfachen Kompilierungszeitfehlers zu erzeugen.

Was vermisse ich?

War es hilfreich?

Lösung

Es wird überwiegend für verwendet Abhängigkeitsspritze, wie im Kuchenmuster. Es gibt a großartiger Artikel Abdeckung vieler verschiedener Formen der Abhängigkeitsinjektion in Scala, einschließlich des Kuchenmusters. Wenn Sie "Kuchenmuster und Scala" googeln, erhalten Sie viele Links, einschließlich Präsentationen und Videos. Im Moment ist hier ein Link zu eine andere Frage.

Nun, was den Unterschied zwischen einem Selbsttyp und dem Erweiterung eines Merkmals ist, ist einfach. Wenn du sagst B extends A, dann B ist ein A. Wenn Sie Selbsttypen verwenden, B erfordert ein A. Es gibt zwei spezifische Anforderungen, die mit Selbsttypen erstellt werden:

  1. Wenn B ist erweitert, dann sind Sie erforderlich zu mischen an A.
  2. Wenn sich eine konkrete Klasse endlich diese Eigenschaften erstreckt/mischt, muss eine Klasse/Merkmale implementiert werden A.

Betrachten Sie die folgenden Beispiele:

scala> trait User { def name: String }
defined trait User

scala> trait Tweeter {
     |   user: User =>
     |   def tweet(msg: String) = println(s"$name: $msg")
     | }
defined trait Tweeter

scala> trait Wrong extends Tweeter {
     |   def noCanDo = name
     | }
<console>:9: error: illegal inheritance;
 self-type Wrong does not conform to Tweeter's selftype Tweeter with User
       trait Wrong extends Tweeter {
                           ^
<console>:10: error: not found: value name
         def noCanDo = name
                       ^

Wenn Tweeter war eine Unterklasse von User, Es würde keinen Fehler geben. In dem obigen Code, wir erforderlich a User Wann immer Tweeter wird verwendet, wie auch immer User wurde nicht zur Verfügung gestellt Wrong, Also haben wir einen Fehler. Bedenken Sie nun, wenn der obige Code noch in den Geltungsbereich ist:

scala> trait DummyUser extends User {
     |   override def name: String = "foo"
     | }
defined trait DummyUser

scala> trait Right extends Tweeter with User {
     |   val canDo = name
     | }
defined trait Right 

scala> trait RightAgain extends Tweeter with DummyUser {
     |   val canDo = name
     | }
defined trait RightAgain

Mit Right, die Anforderung zum Mischen von a User ist befriedigt. Die oben erwähnte zweite Anforderung ist jedoch nicht erfüllt: die Last der Umsetzung User bleibt immer noch für Klassen/Merkmale, die sich erstrecken Right.

Mit RightAgain Beide Anforderungen sind erfüllt. EIN User und eine Implementierung von User sind bereitgestellt.

Weitere praktische Anwendungsfälle finden Sie in den Links zu Beginn dieser Antwort! Aber hoffentlich bekommst du es jetzt.

Andere Tipps

Mit Selbsttypen können Sie zyklische Abhängigkeiten definieren. Zum Beispiel können Sie dies erreichen:

trait A { self: B => }
trait B { self: A => }

Vererbung verwendet extends erlaubt das nicht. Versuchen:

trait A extends B
trait B extends A
error:  illegal cyclic reference involving trait A

Schauen Sie sich im Buch von Odensky Abschnitt 33.5 (Erstellen von Tabellenkalkulationskapitel erstellen).

Im Beispiel der Tabelle erbt das Klassenmodell vom Evaluator und gewinnt somit Zugriff auf seine Bewertungsmethode. Um in die andere Richtung zu gehen, definiert der Klassenbewerter seinen Selbsttyp als Modell wie folgt:

package org.stairwaybook.scells
trait Evaluator { this: Model => ...

Hoffe das hilft.

Ein weiterer Unterschied besteht darin, dass Selbsttypen Nicht-Klassentypen angeben können. Zum Beispiel

trait Foo{
   this: { def close:Unit} => 
   ...
}

Der Selbsttyp hier ist ein Strukturart. Der Effekt ist zu sagen, dass alles, was in Foo mischt, eine nicht-arg-"-Methode zurückgegeben werden muss. Dies ermöglicht sichere Mixins für Enten-Typing.

Abschnitt 2.3 "Selftype -Anmerkungen" von Martin Odenkys ursprünglichem Scala -Papier Skalierbare Komponentenabstraktionen Erklärt tatsächlich den Zweck von Selftype Beyond Mixin -Komposition sehr gut: Bieten Sie eine alternative Möglichkeit, eine Klasse mit einem abstrakten Typ zu verbinden.

Das in der Zeitung angegebene Beispiel war wie das folgende und es scheint keinen eleganten Subklass -Korrespondent zu haben:

abstract class Graph {
  type Node <: BaseNode;
  class BaseNode {
    self: Node =>
    def connectWith(n: Node): Edge =
      new Edge(self, n);
  }
  class Edge(from: Node, to: Node) {
    def source() = from;
    def target() = to;
  }
}

class LabeledGraph extends Graph {
  class Node(label: String) extends BaseNode {
    def getLabel: String = label;
    def self: Node = this;
  }
}

Eine andere Sache, die nicht erwähnt wurde: Da Selbsttypen nicht Teil der Hierarchie der erforderlichen Klasse sind, können sie von der Musteranpassung ausgeschlossen werden, insbesondere wenn Sie sich ausführlich gegen eine versiegelte Hierarchie übereinstimmen. Dies ist praktisch, wenn Sie orthogonale Verhaltensweisen modellieren möchten, wie z. B.:

sealed trait Person
trait Student extends Person
trait Teacher extends Person
trait Adult { this : Person => } // orthogonal to its condition

val p : Person = new Student {}
p match {
  case s : Student => println("a student")
  case t : Teacher => println("a teacher")
} // that's it we're exhaustive

TL; DR Zusammenfassung der anderen Antworten:

  • Sie erweitern Typen, die ererbten Typen ausgesetzt sind, Selbsttypen sind jedoch nicht

    z.B: class Cow { this: FourStomachs } ermöglicht es Ihnen, Methoden zu verwenden, die nur Wiederkäuern zur Verfügung stehen, wie z. digestGrass. Merkmale, die die Kuh erweitern, haben jedoch keine solchen Privilegien. Auf der anderen Seite, class Cow extends FourStomachs wird entlarven digestGrass an jeden, der extends Cow .

  • Selbsttypen erlauben zyklische Abhängigkeiten und die Erweiterung anderer Typen nicht

Beginnen wir mit der zyklischen Abhängigkeit.

trait A {
  selfA: B =>
  def fa: Int }

trait B {
  selfB: A =>
  def fb: String }

Die Modularität dieser Lösung ist jedoch nicht so groß, wie sie zuerst erscheinen könnte, da Sie Selbsttypen als SO überschreiben können:

trait A1 extends A {
  selfA1: B =>
  override def fb = "B's String" }
trait B1 extends B {
  selfB1: A =>
  override def fa = "A's String" }
val myObj = new A1 with B1

Wenn Sie jedoch ein Mitglied eines Selbsttyps außer Kraft setzen, verlieren Sie den Zugriff auf das ursprüngliche Mitglied, auf das immer noch durch Super -Vererbung über Super zugegriffen werden kann. Was also wirklich über die Verwendung der Vererbung gewonnen wird, ist:

trait AB {
  def fa: String
  def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }        
trait B1 extends AB
{ override def fb = "B's String" }    
val myObj = new A1 with B1

Jetzt kann ich nicht behaupten, alle Feinheiten des Kuchenmusters zu verstehen, aber es fällt mir auf, dass die Hauptmethode zur Durchsetzung der Modularität eher durch Zusammensetzung als durch Vererbung oder Selbsttypen besteht.

Die Vererbungsversion ist kürzer, aber der Hauptgrund, warum ich die Vererbung gegenüber Selbsttypen bevorzuge, ist, dass ich es viel schwieriger finde, die Initialisierungsreihenfolge mit Selbsttypen zu korrigieren. Es gibt jedoch einige Dinge, die Sie mit Selbsttypen tun können, die Sie mit Vererbung nicht tun können. Selbsttypen können einen Typ verwenden, während die Vererbung ein Merkmal oder eine Klasse erfordert, wie in:

trait Outer
{ type T1 }     
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.

Sie können sogar tun:

trait TypeBuster
{ this: Int with String => }

Obwohl Sie es nie in der Lage sein werden, es zu instanziieren. Ich sehe keinen absoluten Grund, nicht in der Lage zu sein, von einem Typ zu erben, aber ich bin sicher, es wäre nützlich, Pfadkonstruktorklassen und Merkmale zu haben, da wir Konstruktormerkmale / -klassen haben. Wie leider

trait InnerA extends Outer#Inner //Doesn't compile

Wir haben das:

trait Outer
{ trait Inner }
trait OuterA extends Outer
{ trait InnerA extends Inner }
trait OuterB extends Outer
{ trait InnerB extends Inner }
trait OuterFinal extends OuterA with OuterB
{ val myV = new InnerA with InnerB }

Oder dieses:

  trait Outer
  { trait Inner }     
  trait InnerA
  {this: Outer#Inner =>}
  trait InnerB
  {this: Outer#Inner =>}
  trait OuterFinal extends Outer
  { val myVal = new InnerA with InnerB with Inner }

Ein Punkt, der mehr einfühlsam sein sollte, ist, dass Merkmale Klassen erweitern können. Vielen Dank an David Maclver, dass Sie dies darauf hingewiesen haben. Hier ist ein Beispiel aus meinem eigenen Code:

class ScnBase extends Frame
abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
{ val geomR = geomRI }    
trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]

ScnBase Erben aus dem Schwingen Rahmenklasse, so dass sie als Selbsttyp verwendet und dann am Ende (zur Instanziierung) eingemischt werden kann. Jedoch, val geomR muss initialisiert werden, bevor es durch Erben von Merkmalen verwendet wird. Wir brauchen also eine Klasse, um die vorherige Initialisierung durchzusetzen geomR. Die Klasse ScnVista kann dann von mehreren orthogonalen Merkmalen geerbt werden, von denen selbst ererbt werden kann. Die Verwendung mehrerer Typparameter (Generika) bietet eine alternative Form der Modularität.

trait A { def x = 1 }
trait B extends A { override def x = super.x * 5 }
trait C1 extends B { override def x = 2 }
trait C2 extends A { this: B => override def x = 2}

// 1.
println((new C1 with B).x) // 2
println((new C2 with B).x) // 10

// 2.
trait X {
  type SomeA <: A
  trait Inner1 { this: SomeA => } // compiles ok
  trait Inner2 extends SomeA {} // doesn't compile
}

Mit einem Selbsttyp können Sie angeben, welche Typen ein Merkmal mischen dürfen. Zum Beispiel, wenn Sie ein Merkmal mit einem Selbsttyp haben Closeable, und dann weiß dieses Merkmal, dass die einzigen Dinge, die es mischen dürfen, das implementieren müssen Closeable Schnittstelle.

Aktualisieren: Ein Hauptunterschied besteht darin, dass Selbsttypen davon abhängen können mehrere Klassen (ich gebe zu, dass das ein bisschen Eckfall ist). Zum Beispiel können Sie haben

class Person {
  //...
  def name: String = "...";
}

class Expense {
  def cost: Int = 123;
}

trait Employee {
  this: Person with Expense =>
  // ...

  def roomNo: Int;

  def officeLabel: String = name + "/" + roomNo;
}

Dies ermöglicht das Hinzufügen des Hinzufügens Employee mischen nur zu allem, was eine Unterklasse von ist Person und Expense. Dies ist natürlich nur sinnvoll, wenn Expense erweitert Person oder umgekehrt. Der Punkt ist, dass die Verwendung von Selbsttypen verwendet wird Employee Kann unabhängig von der Hierarchie der Klassen sein, von denen es abhängt. Es kümmert sich nicht darum, was sich ausdehnt - wenn Sie die Hierarchie von wechseln Expense vs Person, Sie müssen nicht ändern Employee.

Im ersten Fall kann ein Unter-Trait oder eine Unterklasse von B in die Verwendung von A gemischt werden. B kann also ein abstraktes Merkmal sein.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top