6.3. Linearyzacja

Definiowane do tej pory w tym rozdziale cechy nie miały metod o wspólnych nazwach. Za to obie zdefiniowane w pliku Linearization1.scala cechy definiują metody o takiej samej nazwie — greet.

Plik Linearization1.scala:
trait HelloGreet { def greet = "Hello!" }
trait WelcomeGreet { def greet = "Welcome!" }
class HelloWelcomeGreet extends HelloGreet with WelcomeGreet

Plik Linearization1.scala zawiera również definicję klasy HelloWelcomeGreet, która próbuje wmieszać obie cechy. Próba kompilacji tego pliku kończy się niepowodzeniem.

$ scalac Linearization1.scala
Linearization1.scala:3: error: class HelloWelcomeGreet inherits conflicting members:
  method greet in trait HelloGreet of type => String  and
  method greet in trait WelcomeGreet of type => String
(Note: this can be resolved by declaring an override in class HelloWelcomeGreet.)
class HelloWelcomeGreet extends HelloGreet with WelcomeGreet
      ^
one error found

Kompilator informuje nas o konflikcie związanym z dziedziczeniem obu metod greet przez klasę HelloWelcomeGreet.

Problem można rozwiązać wprowadzając cechę Greet, definiującą abstrakcyjną metodę o nazwie greet, z której obie cechy HelloGreet i WelcomeGreet dziedziczą i nadpisują tą abstrakcyjną metodę. Takie rozwiązanie znajduje się w pliku Linearization2.scala i kompiluje się bez błędów.

Plik Linearization2.scala:
trait Greet { def greet: String }
trait HelloGreet extends Greet { override def greet = "Hello!" }
trait WelcomeGreet extends Greet { override def greet = "Welcome!" }
class HelloWelcomeGreet extends HelloGreet with WelcomeGreet
class WelcomeHelloGreet extends WelcomeGreet with HelloGreet

W pliku Linearization2.scala zdefiniowane są również klasy, które mają wmieszane cechy HelloGreet i WelcomeGreet, ale w różnej kolejności. Poniższe polecenia pokazują efekt wywołania metody greet na instancjach tych klas.

scala> (new HelloWelcomeGreet).greet
res0: String = Welcome!

scala> (new WelcomeHelloGreet).greet
res1: String = Hello!

Efekt wywołania metody greet jest różny w przypadku instancji obu klas. Obie klasy różnią się kolejnością, w której wymienione są cechy w ich definicjach. W obu przypadkach wywołana została metoda z tej cechy, która została umieszczona w definicji klasy bardziej na prawo. Definicja cechy Welcome2Greet w pliku Linearization3.scala jest zmodyfikowana, w porównaniu z definicją cechy WelcomeGreet. Cecha Welcome2Greet nie dziedziczy z cechy Greet, ale z HelloGreet.

Plik Linearization3.scala:
trait Welcome2Greet extends HelloGreet {
  override def greet = "Welcome!"
}
class HelloWelcome2Greet extends HelloGreet with Welcome2Greet
class Welcome2HelloGreet extends Welcome2Greet with HelloGreet

Jeśli teraz wykonamy podobne jak wcześniej przykłady, ale z użyciem klas, które są zdefiniowane w pliku Linearization3.scala, to okaże się, że w przeciwieństwie do wcześniejszej sytuacji, teraz w obu przypadkach metoda greet zwraca taki sam rezultat.

scala> (new HelloWelcome2Greet).greet
res2: String = Welcome!

scala> (new Welcome2HelloGreet).greet
res3: String = Welcome!

Uzyskany efekt jest związany z zasadami tak zwanej linearyzacji klas. Linearyzacja określonej klasy określa pewien porządek (kolejność) klas i cech, z których ta klasa bezpośrednio lub pośrednio dziedziczy. Jeśli jakaś składowa jest zdefiniowana w wielu takich klasach lub cechach, to pierwszeństwo ma składowa z tej klasy lub cechy, która występuje wcześniej w porządku określanym przez linearyzację.

Zapamiętanie poniższych dwóch reguł może pomóc w określaniu kolejności, w jakiej występują dwie klasy w porządku linearyzacji. Po pierwsze, jeśli jakaś klasa/cecha B dziedziczy pośrednio lub bezpośrednio z klasy/cechy A, to w porządku linearyzacji B ma pierwszeństwo przed A. Po drugie, pierwszeństwo ma ta z dwóch klas/cech, która jest umieszczona w definicji klasy/cechy trzeciej bardziej z prawej strony. Druga z tych reguł ma zastosowanie dopiero, gdy pierwsza nie może być zastosowana, to znaczy gdy dwie klasy/cechy nie są wzajemnie w relacji dziedziczenia (to znaczy, gdy ani pierwsza nie dziedziczy z drugiej, ani druga z pierwszej).

W przykładzie z pliku Linearization3.scala cecha Welcome2Greet dziedziczy z HelloGreet. W związku z tym zastosowanie znalazła tutaj pierwsza z przytoczonych reguł. Cecha Welcome2Greet jest przed cechą HelloGreet w porządku linearyzacji zarówno klasy HelloWelcome2Greet jak i klasy Welcome2HelloGreet. W przypadku instancji obu tych klas, wywołanie metody greet powoduje wywołanie metody z cechy Welcome2Greet.

W przykładzie z pliku Linearization2.scala, cechy HelloGreet oraz WelcomeGreet nie są względem siebie w relacji dziedziczenia, a więc zastosowanie ma druga z przytoczonych reguł. Wobec tego kolejność, w jakiej występują te cechy w porządku linearyzacji klas HelloWelcomeGreet i WelcomeHelloGreet, zależy od ich wzajemnego uporządkowania w definicji każdej z tych klas. W przypadku klasy HelloWelcomeGreet, cecha WelcomeGreet jest umieszczona bardziej z prawej strony i metoda w niej zdefiniowana jest wywołana w przypadku wywołania metody greet na instancji klasy HelloWelcomeGreet. W przypadku klasy WelcomeHelloGreet jest odwrotnie. Bardziej z prawej strony występuje w jej definicji cecha HelloGreet i wobec tego w przypadku wywołania metody greet na instancji klasy WelcomeHelloGreet, następuje odwołanie do metody zdefiniowanej w HelloGreet.

Przedstawione powyżej reguły są interpretacją zasad linearyzacji określonych w specyfikacji języka Scala. Formalna definicja tych zasad znajduje się w punkcie 5.1.2 specyfikacji.

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.