6.1. Definiowanie cech

Scala umożliwia definiowanie tak zwanych cech, które są pod pewnymi względami podobne do zwykłych klas, ale mają też własności odróżniające je od nich. Cechy tworzymy używając słowa kluczowego trait. W pliku Traits1.scala zdefiniowane są: zwykła klasa Hi oraz cecha Hello.

Plik Traits1.scala:
class Hi { def hi = "Hi!" }
trait Hello { def hello = "Hello!" }

Próba utworzenia instancji w następujących przykładach ujawnia jedną z różnic między zwykłymi klasami, a cechami.

scala> val a = new Hi
a: Hi = Hi@a85c4

scala> a.hi
res0: String = Hi!

scala> val b = new Hello
<console>:10: error: trait Hello is abstract; cannot be instantiated
       val b = new Hello
               ^

Nie jest możliwe utworzenie obiektu będącego instancją cechy Hello. Można natomiast utworzyć obiekt będący instancją klasy anonimowej, opartej o cechę Hello. Można to zrobić dodając do wyrażenia new Hello parę nawiasów klamrowych. To samo zresztą można zrobić w przypadku klasy Hi.

scala> val c = new Hi {}
c: Hi = $anon$1@bee915

scala> c.hi
res1: String = Hi!

scala> val d = new Hello {}
d: Hello = $anon$1@18bd2ba
scala> d.hello
res2: String = Hello!

Wartość d ma tak zwany typ złożony. Typy złożone opisane są w punkcie 6.2.

W pliku Traits2.scala znajduje się definicja klasy HiWithHello. Klasa ta rozszerza klasę Hi. Dodatkowo, w jej definicji umieszczone jest słowo kluczowe with, a po nim nazwa cechy Hello. Taki zapis oznacza, że klasa HiWithHello rozszerza klasę Hi oraz że dodatkowo ma „wmieszaną” cechę Hello.

Plik Traits2.scala:
class HiWithHello extends Hi with Hello

Na instancji tej klasy można wywołać zarówno metodę hi, zdefiniowaną w klasie Hi, jak i metodę hello, zdefiniowaną w cesze Hello.

scala> val e = new HiWithHello
e: HiWithHello = HiWithHello@16e2263

scala> e.hi + e.hello
res3: String = Hi!Hello!

W przedstawionym przykładzie typ wartości e nie jest jawnie określony i kompilator samodzielnie wyciąga wniosek dotyczący jej typu. Można również jawnie zadeklarować typ takiej wartości i typem tym nie musi być HiWithHello. Można użyć jako typu klasy, z której ta klasa dziedziczy, jak również cechy wmieszanej w tę klasę. Jeszcze inne możliwości są opisane w punkcie 6.2. Obydwie poniższe definicje są prawidłowe.

scala> val f: Hi = new HiWithHello
f: Hi = HiWithHello@944ca9

scala> val g: Hello = new HiWithHello
g: Hello = HiWithHello@15b0487

Na obiektach f i g nie można wywołać niektórych metod dostępnych w klasie HiWithHello. To, które metody są dostępne, zależy od tego, jak zadeklarowany jest typ odpowiedniej instancji.

scala> f.hi
res4: String = Hi!

scala> g.hello
res5: String = Hello!

scala> f.hello
<console>:12: error: value hello is not a member of Hi
       f.hello
         ^

scala> g.hi
<console>:12: error: value hi is not a member of Hello
       g.hi
         ^

Zwykłe klasy nie mogą być wmieszane w inną klasę, a więc ich nazwa nie może być użyta po słowie kluczowym with.

Plik Traits3.scala:
class HelloWithHi extends Hello with Hi

Definicja klasy HelloWithHi z pliku Traits3.scala jest błędna z tego powodu i nie kompiluje się.

$ scalac Traits3.scala
Traits3.scala:1: error: class Hi needs to be a trait to be mixed in
class HelloWithHi extends Hello with Hi
                                     ^
one error found

Klasa może dziedziczyć tylko z jednej klasy, ale może mieć wmieszaną więcej niż jedną cechę. W definicji klasy można podać więcej niż jedno słowo kluczowe with, z następującą po nim nazwą cechy. W pliku Traits4.scala jest zdefiniowana klasa HiWithHelloAndWelcome, która dziedziczy z klasy Hi oraz ma wmieszane cechy Hello i Welcome.

Plik Traits4.scala:
trait Welcome { def welcome = "Welcome!" }
class HiWithHelloAndWelcome extends Hi with Hello with Welcome

Na instancji tej klasy można wywoływać metody hi, hello oraz welcome.

scala> val k = new HiWithHelloAndWelcome
k: HiWithHelloAndWelcome = HiWithHelloAndWelcome@199c6b5

scala> k.hi
res0: String = Hi!

scala> k.hello
res1: String = Hello!

scala> k.welcome
res2: String = Welcome!

Definicja nowej klasy może pominąć jawne określenie po słowie kluczowym extends klasy, z której ta definiowana klasa ma dziedziczyć. W takim przypadku klasa dziedziczy z klasy AnyRef. Nie jest jednak możliwe użycie słowa kluczowego with do wmieszania cechy z jednoczesnym pominięciem słowa kluczowego extends. W przypadku, gdy nie chcemy określać jawnie klasy, z której nowa klasa będzie dziedziczyć, a chcemy podać jedną lub więcej cech, mających być wmieszanych do nowej klasy, należy nazwę pierwszej z tych cech poprzedzić słowem kluczowym extends, a dopiero kolejne poprzedzać słowem kluczowym with.

Plik Traits5.scala:
class AnyRefWithHello extends Hello
class AnyRefWithHelloWithWelcome extends Hello with Welcome

W ten sposób zdefiniowane są klasy AnyRefWithHello oraz AnyRefWithHelloWithWelcome z pliku Traits5.scala.

scala> val m = new AnyRefWithHello
m: AnyRefWithHello = AnyRefWithHello@1e731e

scala> val n = new AnyRefWithHelloWithWelcome
n: AnyRefWithHelloWithWelcome = AnyRefWithHelloWithWelcome@1ff43be

scala> m.hello
res3: String = Hello!

scala> n.hello
res4: String = Hello!

scala> n.welcome
res5: String = Welcome!

Oprócz definiowania zwykłych klas, które mają wmieszane cechy, możemy również definiować nowe cechy, które rozszerzają jedną lub więcej cech. Na przykład cecha HelloWithWelcome, zdefiniowana w pliku Traits6.scala, rozszerza cechy Hello i Welcome.

Plik Traits6.scala:
trait HelloWithWelcome extends Hello with Welcome

W dalszej części książki będę pisał o „dziedziczeniu” zarówno w przypadku składowych pochodzących ze zwykłych klas, jak i pochodzących z cech.

Specyfikacja języka Scala opisuje cechy w punkcie 5.4.

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.