Scala Language
Cechy
Szukaj…
Składnia
- cecha ATrait {...}
- klasa AClass (...) rozszerza ATrait {...}
- klasa AClass rozszerza klasę BClass o ATrait
- klasa AClass rozszerza ATrait o BTrait
- klasa AClass rozszerza ATrait o BTrait o CTrait
- klasa ATrait rozszerza BTrait
Modyfikacja stosów z cechami
Możesz użyć cech, aby zmodyfikować metody klasy, używając cech w sposób umożliwiający ustawianie jeden na drugim.
Poniższy przykład pokazuje, w jaki sposób można zestawiać cechy. Kolejność cech jest ważna. Stosując inną kolejność cech, osiąga się różne zachowanie.
class Ball {
def roll(ball : String) = println("Rolling : " + ball)
}
trait Red extends Ball {
override def roll(ball : String) = super.roll("Red-" + ball)
}
trait Green extends Ball {
override def roll(ball : String) = super.roll("Green-" + ball)
}
trait Shiny extends Ball {
override def roll(ball : String) = super.roll("Shiny-" + ball)
}
object Balls {
def main(args: Array[String]) {
val ball1 = new Ball with Shiny with Red
ball1.roll("Ball-1") // Rolling : Shiny-Red-Ball-1
val ball2 = new Ball with Green with Shiny
ball2.roll("Ball-2") // Rolling : Green-Shiny-Ball-2
}
}
Zauważ, że super
służy do wywoływania metody roll()
w obu cechach. Tylko w ten sposób możemy osiągnąć modyfikację stosową. W przypadku modyfikacji stosu kolejność wywoływania metod jest określana przez regułę linearyzacji .
Podstawy cechy
Jest to najbardziej podstawowa wersja cechy w Scali.
trait Identifiable {
def getIdentifier: String
def printIndentification(): Unit = println(getIdentifier)
}
case class Puppy(id: String, name: String) extends Identifiable {
def getIdentifier: String = s"$name has id $id"
}
Ponieważ żadna AnyRef
nie jest zadeklarowana dla cechy Identifiable
, domyślnie rozciąga się AnyRef
klasę AnyRef
. Ponieważ w Identifiable
podano definicji getIdentifier
, klasa Puppy
musi ją zaimplementować. Jednak Puppy
dziedziczy implementację printIdentification
po Identifiable
.
W REPL:
val p = new Puppy("K9", "Rex")
p.getIdentifier // res0: String = Rex has id K9
p.printIndentification() // Rex has id K9
Rozwiązywanie problemu diamentów
Problem z diamentem lub wielokrotne dziedziczenie jest rozwiązywany przez Scalę za pomocą Cech, które są podobne do interfejsów Java. Cechy są bardziej elastyczne niż interfejsy i mogą obejmować zaimplementowane metody. To sprawia, że cechy są podobne do mixin w innych językach.
Scala nie obsługuje dziedziczenia z wielu klas, ale użytkownik może rozszerzyć wiele cech w jednej klasie:
trait traitA {
def name = println("This is the 'grandparent' trait.")
}
trait traitB extends traitA {
override def name = {
println("B is a child of A.")
super.name
}
}
trait traitC extends traitA {
override def name = {
println("C is a child of A.")
super.name
}
}
object grandChild extends traitB with traitC
grandChild.name
Tutaj grandChild
dziedziczy po obu traitB
i traitC
, które z kolei dziedziczą po traitA
Dane wyjściowe (poniżej) pokazują również kolejność pierwszeństwa przy ustalaniu, które implementacje metod są wywoływane jako pierwsze:
C is a child of A.
B is a child of A.
This is the 'grandparent' trait.
Zauważ, że gdy super
jest używane do wywoływania metod w class
lub trait
, w grę wchodzi reguła linearyzacji , która decyduje o hierarchii połączeń. Kolejność linearyzacji dla grandChild
będzie:
grandChild -> traitC -> traitB -> traitA -> AnyRef -> Any
Poniżej znajduje się inny przykład:
trait Printer {
def print(msg : String) = println (msg)
}
trait DelimitWithHyphen extends Printer {
override def print(msg : String) {
println("-------------")
super.print(msg)
}
}
trait DelimitWithStar extends Printer {
override def print(msg : String) {
println("*************")
super.print(msg)
}
}
class CustomPrinter extends Printer with DelimitWithHyphen with DelimitWithStar
object TestPrinter{
def main(args: Array[String]) {
new CustomPrinter().print("Hello World!")
}
}
Ten program drukuje:
*************
-------------
Hello World!
Linearyzacja dla CustomPrinter
będzie:
CustomPrinter -> DelimitWithStar -> DelimitWithHyphen -> Drukarka -> AnyRef -> Dowolny
Linearyzacja
W przypadku modyfikacji , którą można ustawiać jeden na drugim , Scala układa klasy i cechy w kolejności liniowej, aby określić hierarchię wywołań metod, zwaną linearyzacją . Reguła linearyzacji jest używana tylko dla tych metod, które wymagają wywołania metody przez super()
. Rozważmy to na przykładzie:
class Shape {
def paint (shape: String): Unit = {
println(shape)
}
}
trait Color extends Shape {
abstract override def paint (shape : String) {
super.paint(shape + "Color ")
}
}
trait Blue extends Color {
abstract override def paint (shape : String) {
super.paint(shape + "with Blue ")
}
}
trait Border extends Shape {
abstract override def paint (shape : String) {
super.paint(shape + "Border ")
}
}
trait Dotted extends Border {
abstract override def paint (shape : String) {
super.paint(shape + "with Dotted ")
}
}
class MyShape extends Shape with Dotted with Blue {
override def paint (shape : String) {
super.paint(shape)
}
}
Linearyzacja odbywa się od tyłu do przodu . W tym przypadku,
Pierwszy
Shape
zostanie zlinearyzowany, co wygląda następująco:Shape -> AnyRef -> Any
Następnie
Dotted
jest linearyzowany:Dotted -> Border -> Shape -> AnyRef -> Any
Następny w kolejce jest
Blue
. Zazwyczaj linearyzacjaBlue
będzie:Blue -> Color -> Shape -> AnyRef -> Any
ponieważ, w
MyShape
linearyzacjiMyShape
( Krok 2 ),Shape -> AnyRef -> Any
już się pojawił. Dlatego jest ignorowany. Zatem linearyzacjaBlue
będzie:Blue -> Color -> Dotted -> Border -> Shape -> AnyRef -> Any
Na koniec zostanie dodany
Circle
, a ostateczna kolejność linearyzacji będzie:Okrąg -> Niebieski -> Kolor -> Kropkowany -> Obramowanie -> Kształt -> AnyRef -> Dowolny
Ta kolejność linearyzacji decyduje o kolejności wywoływania metod, gdy super
jest używana w dowolnej klasie lub cechy. Wywoływana jest implementacja pierwszej metody z prawej strony, w kolejności linearyzacji. Jeśli zostanie wykonane new MyShape().paint("Circle ")
, zostanie wydrukowane:
Circle with Blue Color with Dotted Border
Więcej informacji na temat linearyzacji można znaleźć tutaj .