Trait jest czymś pomiędzy interface a class -> można w nim implementować metody, można je zostawiać abstrakcyjne. Można również również dodać pola, choć kostruktorów już nie można utworzyć.
No i tak właściwie - na pierwszy rzut oka wydaje się praktycznie tym samym co klasa abstrakcyjna, z dwoma jedynie rożnicami - klasa abstrakcyjna może konstruktor, no i można dziedziczyć tylko po jednej klasie abstrakcyjnej.
Tak właściwie to czy ma to jakies dużo większe zastosowana? Czy daje to jakąś przewage nad stosowaniem klas abstrakcyjnych? Ja ogólnie nie za bardzo lubie dziedziczenia więc jakoś niezbyt entuzjastycznie podchodze to tego typu konstrukcji :D
@Wibowit @jarekr000000 wzywam :)
- Rejestracja:ponad 10 lat
- Ostatnio:5 miesięcy
- Lokalizacja:Warszawa
- Postów:3573

- Rejestracja:ponad 17 lat
- Ostatnio:17 minut
- Lokalizacja:Warszawa
- Postów:2251
W sumie może odpowiem na bazie tego jak traity działają w phpie, no więc ...
- klasa nie musi dziedziczyć po klasie abstrakcyjnej
- klasa nie musi implementować konkretnego interfejsu
- klasa może mieć wiele traitów
patrząc na powyższe - w przypadku niektórych zmian w kodzie, nie trzeba dowalać interfejsu czy klasy abstrakcyjnej. W moim odczuciu jest to uproszczenie, które pozwala uniknąć duplikacji kodu, jednocześnie nie narzucając powyższych wymagań - co przy istniejących klasach może narobić bigosu. No i skoro nie lubisz dziedziczenia, to trait może być właśnie dla Ciebie :) No ale wiesz... mówię o traitach w PHP... a to jest magia :P ( @jarekr000000 jako nasz naczelny czarodziej może jednak powie coś mądrzejszego)
- Rejestracja:ponad 10 lat
- Ostatnio:5 miesięcy
- Lokalizacja:Warszawa
- Postów:3573
@axelbest: nie wiem czy trairty w PHP działają tak samo, ale w sumie czym się de facto różni dziedzicenie czy tam implementacja traitu od dziedziczenia po klasie (poza tym że można dziedziczyć tylko po jednej klasie). Generalnie dziedziczenie jest dosyć silnie wiążace i ograniczania, więc staram się unikać dziedziczenia i wole kompozycje + interfejsy

- Rejestracja:ponad 13 lat
- Ostatnio:około 3 lata
- Lokalizacja:Grudziądz/Bydgoszcz
- Postów:5300
trait to nie klasa, nie ma żadnego obiektu, to jest w zasadzie np jakaś metoda która w zupełnie róznych klasach, nic ze soba niepowiązanych wygląda tak samo i żeby nie pisać w każdej klasie tej metody robi się traita i wrzuca do takiej klasy
- Rejestracja:ponad 10 lat
- Ostatnio:5 miesięcy
- Lokalizacja:Warszawa
- Postów:3573
No tak jak klasa abstrakcjna. może mieć również pola. Co to za różnica w takim razie bo ja nie widze :D

- Rejestracja:około 11 lat
- Ostatnio:ponad 3 lata
- Postów:459
Sam z traitów korzystam wtedy gdy chcę dodać jakieś funkcje do moich klas, a nie chcę aby one w jakiś sztuczny sposób dziedziczyły po jakiejś nadklasie albo duplikowały ten sam kod, gdyby miały implementować jakiś interejs. Mixiny w Rubim oferują to samo (tyle, że też dodają rzeczy do stdlib :) ).

- Rejestracja:prawie 10 lat
- Ostatnio:ponad rok
- Lokalizacja:Wrocław
- Postów:107
Nie napisałeś o jaki język chodzi, założę że Scala.
Jest jeszcze wykorzystanie traitow do wzorca 'stackable modifications'.
Jak dla mnie taki decorator na sterydach.
A ogólnie to widziałem to zawsze podobnie; interfejsy z metodami i polami + bezpieczne pseudo wielodziedziczenie.


- Rejestracja:ponad 10 lat
- Ostatnio:5 miesięcy
- Lokalizacja:Warszawa
- Postów:3573
Pipes napisał(a):
Sam z traitów korzystam wtedy gdy chcę dodać jakieś funkcje do moich klas, a nie chcę aby one w jakiś sztuczny sposób dziedziczyły po jakiejś nadklasie albo duplikowały ten sam kod, gdyby miały implementować jakiś interejs. Mixiny w Rubim oferują to samo (tyle, że też dodają rzeczy do stdlib :) ).
W Scalii czy Groovym można zrobić te metody z Traitow finalne więc częściowo masz racje, ale czy w takim przypadku lepiej nie zrobić kompozycji? Generalnie bardzo rzadko spotykam się z tym żeby dziedziczenie było lepsze :D
- Rejestracja:prawie 10 lat
- Ostatnio:5 miesięcy
- Postów:141
W sumie to większość różnic sam już znalazłeś ;) Najważniejsze z nich to:
- Trait nie posiada argumentów (chociaż to ma się zmienić w Dotty)
- Możesz rozszerzyć tylko jedną klasę abstrakcyjną ale za to mix-in'ować wiele traitów
- Traity się stackują oraz podlegają linearyzacji
Traity często się stosuje w przypadku:
- Dodania do klasy konkretnego zachowania, np. JsonWriter, JsonReader lub JsonFormat.
- Tworzenia ADT (chociaż tutaj można się posługiwać klasą abstrakcyjną i niektórzy uważają (Sam Halliday), że tak powinno się je robić)
- Type Class'y
- Phantom types (aczkolwiek to nie jest za często stosowane)
I pewnie masa wielu innych zastosowań o których nie pomyślałem teraz.
Oprócz tego patrząc z punktu koncepcji to trait'y powinny być stosowane tam, gdzie definiujesz jedno, konkretne, zazwyczaj generyczne zachowanie które może być wykorzystywane w wielu miejscach.
- Rejestracja:ponad 8 lat
- Ostatnio:ponad 6 lat
- Postów:990
Traity wymyślili gdy Java zrezygnowała z wielodziedziczenia. Jak widać nazwy się c zmieniają ale duch dalej silny :D

- Rejestracja:ponad 10 lat
- Ostatnio:5 miesięcy
- Lokalizacja:Warszawa
- Postów:3573
No ja właśnie tak traktuje traity, rozwiązanie problemu z wielodziedziczeniem :D Ale np. w Kotlinie traity zostały wywalone :)

- Rejestracja:prawie 10 lat
- Ostatnio:około 12 godzin
- Postów:2367
Odgrzebałem jeden stary artykuł, w którym traity są omawiane jako sposób realizacji pewnego pomysłu. https://www.artima.com/articles/dci_vision.html

- Rejestracja:prawie 20 lat
- Ostatnio:około 5 godzin
Interfejsy w Javie 8 pozwalają na wielodziedziczenie zachowań. Traity ze Scali pozwalały na to od dawna (nawet gdy Scala chodziła na Javie 5), ale dodatkowo pozwalają na dziedziczenie stanu - to akurat dość rzadko widuję, no chyba że w testach (ludzie mają tendencję na dopchanie do pojedynczego speca (klasy z zestawem testów) maksimum funkcjonalności, nawet nieużywanych lub źle zaprojektowanych).
Linearyzacja traitów (i co za tym idzie stackable traits) to pewien postęp w stosunku do wywalania się w czasie kompilacji na diamond problem. Typeclassy nie rozwiązują problemu, bo co jeśli dwie różne typeclassy zawierające identyczną metodę zostaną zaimplementowane dla jednego typu? Powracamy do diamond problem tylko w innej postaci. W Scali podpada to pod ambiguous implicits które są błędem kompilacji. Przykład: https://www.ideone.com/XdkJQK
Osobiście preferuję klasy abstrakcyjne nad traity z tego powodu, że klasy abstrakcyjne w Scali mają lżejsze kodowanie do bajtkodu niż traity. W końcu Scalowe klasy abstrakcyjne są praktycznie Javową konstrukcją, więc narzutu na nie nie ma. Natomiast cała linearyzacja traitów, wielodziedziczenie stanu, etc wymagają tworzenia dodatkowych syntetycznych metod co zarówno wydłuża czas kompilacji jak i zwiększa rozmiar wynikowego bajtkodu. Być może różnica nie jest wielka, ale zarazem wpisanie "abstract class" zamiast "trait" to też nie jest wielka różnica.
Najczęściej spotykane sensowne wykorzystanie wielodziedziczenia zachowania to sama hierarchia kolekcji Scalowych. Innym przykładem jest (tworzona przez użytkownika biblioteki ScalaTest) klasa bazowa dla testów: http://www.scalatest.org/user_guide/defining_base_classes - dodatkowo jest to przykład na wielodziedziczenie stanu, bo traity ze ScalaTesta często posiadają własny stan. W ogólności wielodziedziczenie implementacji, a już na pewno stanu należy stosować z rozwagą. Z drugiej strony implementowanie wielu traitów bez stanu i implementacji jest dość bezpieczne, aczkolwiek nie zawsze jest sens implementowania multum traitów przez jedną klasę.
@DisQ: Czy implicit classes pozwalają mi w razie konfliktu skorzystać z metod Traitu A a w przypadku konfliktu na metodzie z jej implementacji w Traicie B? Jak nie to nie mówimy o tym samym. Co do rozwiązywania konfliktów poprzez precedencję w deklaracji - dziękuję, postoję ;) - loza_szydercow 34 minuty temu
Kolejność deklaracji nie ma znaczenia, ale głębokość dziedziczenia owszem. W Scali często traity nazywa się LowPriorityImplicits i jest to pewien wzorzec pozwalający w pewnych przypadkach (a więc częściowo) rozwiązać diamond problem. Przykład: https://stackoverflow.com/a/33544705
Rozwiązywanie konfliktów implicitów przez kolejność implicitów w zasięgu też da się zrobić wykorzystując mechanizm przesłaniania. Wystarczy, że oba implicity mają tę samą nazwę - kompilator zobaczy tylko tą która jest zaimportowana jako ostatnia, a więc w kodzie:
import konwersjeA.konwersjaImplicit
import konwersjeB.konwersjaImplicit
// kod wykorzystujące konwersjaImplicit
wykorzystana zostanie konwersja implicit pochodząca z konwersjeB
.
Mechanizm przesłaniania wymaga importowania składowych do zasięgu, nie można przesłonić zmiennej deklarując ją dwukrotnie w tej samej przestrzeni nazw, np:
object CośTam {
val x: Int = 5
val x: String = "ala ma kota" // błąd - tak nie zrobimy przesłaniania nazw
}