Trait - po co to?

1

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 :)

2

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)

0

@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

0

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

0

No tak jak klasa abstrakcjna. może mieć również pola. Co to za różnica w takim razie bo ja nie widze :D

0

klasa abstrakcyjna wymaga posiadania metody abstrakcyjnej która trzeba zaimplementować no i trzeba po tej klasie dziedziczyć, ani jednego ani drugiego nie wymaga trait

0

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 :) ).

0

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.

0
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

1

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.

0

Traity wymyślili gdy Java zrezygnowała z wielodziedziczenia. Jak widać nazwy się c zmieniają ale duch dalej silny :D

0

No ja właśnie tak traktuje traity, rozwiązanie problemu z wielodziedziczeniem :D Ale np. w Kotlinie traity zostały wywalone :)

0

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

3

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
}
0

@Wibowit: czyli w skrócie jeśli dobrze rozumiem to głownie jest po to żeby ominąc problem możliwości dziedziczenia tylko po jednej klasie? :D

1

Generalnie tak, ale diabeł tkwi w szczegółach :]

1 użytkowników online, w tym zalogowanych: 0, gości: 1