Odpowiedzi:
Mogę wymyślić dwie różnice
Istnieje część w Programowaniu w Scali o nazwie „Aby cechować, czy nie cechować?” który odpowiada na to pytanie. Ponieważ pierwsza edycja jest dostępna online, mam nadzieję, że przytoczę tutaj wszystko. (Każdy poważny programista Scala powinien kupić książkę):
Za każdym razem, gdy wdrażasz zbiór zachowań wielokrotnego użytku, będziesz musiał zdecydować, czy chcesz użyć cechy, czy klasy abstrakcyjnej. Nie ma sztywnej zasady, ale w tej sekcji znajduje się kilka wskazówek do rozważenia.
Jeśli zachowanie nie zostanie ponownie wykorzystane , uczyń je konkretną klasą. W końcu nie jest to zachowanie wielokrotnego użytku.
Jeśli można go ponownie wykorzystać w wielu niepowiązanych klasach , uczyń go cechą. Tylko cechy mogą być mieszane w różnych częściach hierarchii klas.
Jeśli chcesz dziedziczyć po nim w kodzie Java , użyj klasy abstrakcyjnej. Ponieważ cechy z kodem nie mają bliskiego analogu Java, odziedziczenie cech po klasie Java jest raczej niewygodne. Tymczasem dziedziczenie z klasy Scala jest dokładnie takie samo jak dziedziczenie z klasy Java. Jako jeden wyjątek, cecha Scala z tylko elementami abstrakcyjnymi tłumaczy bezpośrednio na interfejs Java, więc powinieneś swobodnie definiować takie cechy, nawet jeśli spodziewasz się po nim dziedziczenia kodu Java. Więcej informacji na temat wspólnej pracy z Javą i Scalą znajduje się w rozdziale 29.
Jeśli planujesz dystrybuować go w skompilowanej formie i spodziewasz się, że grupy zewnętrzne będą pisać dziedziczące z niego klasy, możesz skłaniać się ku użyciu klasy abstrakcyjnej. Problem polega na tym, że gdy cecha zyskuje lub traci członka, wszelkie dziedziczące od niej klasy muszą zostać ponownie skompilowane, nawet jeśli nie uległy zmianie. Jeśli klienci zewnętrzni będą wywoływać tylko zachowanie, zamiast dziedziczyć po nim, użycie cechy jest w porządku.
Jeśli wydajność jest bardzo ważna , skłaniaj się ku użyciu klasy. Większość środowisk wykonawczych Java sprawia, że wywołanie metody wirtualnej członka klasy jest szybsze niż wywołanie metody interfejsu. Cechy są kompilowane do interfejsów i dlatego mogą ponieść niewielki koszt wydajności. Jednak powinieneś dokonać tego wyboru tylko wtedy, gdy wiesz, że dana cecha stanowi wąskie gardło wydajności i masz dowody, że użycie klasy faktycznie rozwiązuje problem.
Jeśli nadal nie wiesz , po rozważeniu powyższego, zacznij od uczynienia go cechą. Zawsze możesz go później zmienić i ogólnie rzecz biorąc, użycie cechy pozwala zachować więcej opcji.
Jak wspomniano @Mushtaq Ahmed, cecha nie może mieć żadnych parametrów przekazywanych do głównego konstruktora klasy.
Kolejną różnicą jest leczenie super
.
Inna różnica między klasami a cechami polega na tym, że podczas gdy w klasach
super
wywołania są związane statycznie, w przypadku cech są one dynamicznie powiązane. Jeśli piszeszsuper.toString
w klasie, wiesz dokładnie, która metoda zostanie wywołana. Kiedy piszesz to samo w cechy, jednak implementacja metody, która ma zostać wywołana dla super wywołania, jest niezdefiniowana podczas definiowania cechy.
Więcej informacji znajduje się w dalszej części rozdziału 12 .
Edycja 1 (2013):
Istnieje niewielka różnica w zachowaniu klas abstrakcyjnych w porównaniu do cech. Jedną z reguł linearyzacji jest to, że zachowuje hierarchię dziedziczenia klas, która ma tendencję do popychania klas abstrakcyjnych na później w łańcuchu, podczas gdy cechy mogą być z powodzeniem mieszane. W pewnych okolicznościach faktycznie lepiej jest znajdować się na drugiej pozycji linearyzacji klas , więc można do tego użyć klas abstrakcyjnych. Zobacz ograniczanie linearyzacji klas (kolejność mieszania) w Scali .
Edycja 2 (2018):
Począwszy od Scali 2.12, zachowanie zgodności binarnej cechy uległo zmianie. Przed 2.12 dodanie lub usunięcie elementu do cechy wymagało ponownej kompilacji wszystkich klas, które dziedziczą tę cechę, nawet jeśli klasy się nie zmieniły. Wynika to ze sposobu, w jaki cechy zostały zakodowane w JVM.
Począwszy od Scali 2.12, cechy kompilują się do interfejsów Java , więc wymagania nieco się zmniejszyły. Jeśli cecha spełnia którekolwiek z poniższych kryteriów, jej podklasy nadal wymagają ponownej kompilacji:
- definiowanie pól (
val
lubvar
, ale stała jest w porządku -final val
bez typu wyniku)- powołanie
super
- instrukcje inicjalizujące w treści
- rozszerzenie klasy
- opierając się na linearyzacji w celu znalezienia implementacji w prawej nadbudowie
Ale jeśli ta cecha nie, możesz ją teraz zaktualizować bez naruszenia zgodności binarnej.
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- Czy ktoś mógłby wyjaśnić, jaka jest tutaj różnica? extends
kontra with
?
extends
i with
. Jest to czysto składniowe. Jeśli odziedziczysz po wielu szablonach, pierwszy dostaje extend
, wszystkie inne dostają with
, to wszystko. Pomyśl with
jak przecinkami: class Foo extends Bar, Baz, Qux
.
Niezależnie od tego, co jest warte, Odersky i wsp. Programowanie w Scali zaleca, aby w razie wątpliwości używać cech. W razie potrzeby zawsze możesz zmienić je na klasy abstrakcyjne.
Poza tym, że nie można bezpośrednio rozszerzyć wielu klas abstrakcyjnych, ale można łączyć wiele cech w klasę, warto wspomnieć, że cechy można ustawiać jeden na drugim, ponieważ super wywołania cechy są dynamicznie powiązane (odnosi się to do klasy lub cechy mieszanej wcześniej obecne).
Z odpowiedzi Thomasa na Różnice między klasą abstrakcyjną a cechą :
trait A{
def a = 1
}
trait X extends A{
override def a = {
println("X")
super.a
}
}
trait Y extends A{
override def a = {
println("Y")
super.a
}
}
scala> val xy = new AnyRef with X with Y
xy: java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1
scala> val yx = new AnyRef with Y with X
yx: java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1
W Programowaniu Scali autorzy twierdzą, że klasy abstrakcyjne tworzą klasyczną relację obiektową typu „jest-a”, podczas gdy cechy są skalowaną kompozycją.
Klasy abstrakcyjne mogą zawierać zachowanie - można sparametryzować je za pomocą argumentów konstruktora (których cechy nie mogą) i reprezentować działającą jednostkę. Zamiast tego cechy reprezentują tylko jedną funkcję, interfejs jednej funkcjonalności.
trait Enumerable
pomocą wielu funkcji pomocniczych, nie nazwałbym ich zachowaniem, a jedynie funkcjonalnością związaną z jedną funkcją.