Czy mógłby mi ktoś wytłumaczyć do czego służą interfejsy i klasy abstrakcyjne?

0

Nie rozumiem dokładnie... Prosiłbym o wytłumaczenie... Nie żadne linki do artykułów czy filmików.

1

Mamy zrobić kolejny artykuł czy filmik?

</ironia>

Napisz coś więcej, nie ma sensu powielać tego co już 100x napisano.

0

No dobra. To może zadanko, które da ci do myślenia. Zaprogramuj sortowanie bąbelkowe tak by mogło sortować dowolne obiekty. Następnie wyjaśnij dlaczego użyłeś interfejsów bądź klas abstrakcyjnych do osiągnięcia celu.

0

Nie chciałeś linków, ale własnymi słowami bym tego jaśniej nie wytłumaczył: http://web.mit.edu/6.005/www/fa15/classes/14-interfaces/.
I mam jeszcze lepsze zadanko. Zaprogramuj, prostą strukturę danych, może być (ale nie musi) stack i niech ma "under the hood" dwie implementacje na LinkedList i ArrayList; zrób to z interfejsem i bez.

0

Interfejs to pusta klasa. Robisz ją po to żeby np do 10 różnych klas zaimplementować np. 5 różnych metod ale nie definiujesz dokładnie jak mają one działać w każdej z klas implementujących ten interfejs. Dopiero po implementacji interfejsu do danej klasy opisujesz jak ma przebiegać realizowanie danej funkcjonalności.

Przykładowo interfejs może zawierać 3 metody: Dystans(), Prędkość(), Zasięg() ale implementujesz go do klas Mercedes, Ferrari, Porsche, Fiat, Dacia. Wszystkie mają to samo ale każdy z nich przejedzie inny dystans, uzyska inną prędkość i będzie mieć inny zasięg.

0

Interfejsy służą łatwemu wymienianiu rozwiązań na to samo zagadnienie. Np. masz zagadnienie „zapisać loga” to piszesz interfejs i potem poszczególne klasy – jedna zapisze do pliku, inna wypisze na standardowe wyjście, a jeszcze inna wyśle na jakiegoś RESTa. Wówczas kod który chce pisać do loga może użyć w polu/parametrze nazwy interfejsu, która może przyjąc każdy obiekt który implementuje ten interfejs.
Klasa abstrakcyjna umożliwia wydzielić kod wspólny dla kilku klas.

0

Załóżmy, że masz podobne obiekty (instancje różnych klas), które mają wspólne metody lub pola. Możesz je umieścić w klasie abstrakcyjnej i wszystkie obiekty mogą z niej dziedziczyć, przez co nie powtarzasz tego samego kodu w każdej klasie (DRY). Pewnie słusznie zauważyłeś, że przecież wystarczy dziedziczyć po zwykłej klasie i otrzymamy to samo. Tak, to prawda, ale różnica jest taka, że nie można utworzyć instancji klasy abstrakcyjnej. Jeśli metoda jest abstrakcyjna, to nie ma sensu tworzyć jej obiektu.

A czym jest interfejs? Załóżmy, że tworzysz walidator, który sprawdza, czy dane wprowadzone przez użytkownika są poprawne. Silnik walidator korzysta z klas, które walidują dane na różne sposoby. Na przykład masz klasę, która sprawdza czy wprowadzona liczba pochodzi z odpowiedniego zakresu. Albo czy wprowadzono numer telefonu (9 cyfr). Możesz sobie dodawać wiele takich klas, które coś sprawdzają, jednak klasy te muszą poprawnie komunikować się z silnikiem walidatora. Możesz to wymusić tym, że każda taka klasa musi implementować zdefiniowany interfejs, zgodny z wymogami silnika. W przeciwnym razie kod nie zadziała.

1

Interfejs to kontrakt na dostarczenie jakiś metod, funkcjonalności. Dla przykładu mamy ExecutorService który służy do uruchomienia różnych zadań. Może być zaimplementowany z użyciem puli wątków, może to być jeden wątek który będzie wykonywał wszystkie zadania po kolei etc. ale dzięki zastosowaniu tego rozwiązania możesz łatwo podmieniać implementacje, nie burząc kodu w środku klasy w której ten interface jest przekazany np. przez konstruktor :)

0

Obczaj sobie np. "factory" jako design pattern to sczaisz do czego są abstrackje/interfejsy.

1

Można o tym cały elaborat napisać.

Niemniej jednak, popularne i praktyczne zastosowanie interfejsów, to np.

  • wstrzykiwanie zależności (dependency injection)
  • realizacja wzorca projektowego strategii (strategy pattern)
  • tworzenie listenerów/klas anonimowych/wyrażeń lambda (np. dla zdarzeń, akcji w GUI, requestów HTTP, etc.)
  • itd.

Praktyczne zastosowanie klas abstrakcyjnych, to np.:

  • realizacja wzorca metody szablonowej (template method pattern)
  • stworzenie klasy, w której chcemy mieć z góry określone implementacje niektórych metod dla klas pochodnych, ale nie chcemy tworzyć instancji tejże klasy
  • itd.

To tylko kilka przykładów, które mi się teraz nasunęły. Może ich być oczywiście więcej. Poczytaj sobie o wzorcach projektowych i przejrzyj ich implementacje w Javie, spróbuj coś zakodować, to sporo Ci się rozjaśni. Możesz też sobie pogooglać hasła, które wymieniłem wyżej.

W Javie 8+ możemy mieć oczywiście domyślne implementacje metod w interfejsach, ale myślę, że powinno mieć to inne zastosowanie, niż klasy abstrakcyjne. Np. gdy utrzymujemy API, chcemy dodać do niego nową metodę, ale nie chcemy łamać kompatybilności wstecznej u konsumentów naszego API.

0

Piszecie... tłumaczycie... ale czy kolega zna odpowiedz na fundamentalne pytanie: co to jest polimorfizm?

1

@PrezesiQ: żeby zrozumieć praktyczne zastosowanie tego po prostu pisz kod...

1

Jak ktoś chociaż chwilę klepał w Javie z minimalnym chociaż zastanowieniem się dlaczego biblioteka standardowa Javy wygląda jak wygląda to zobaczy mnóstwo przykładów na zastosowanie interfejsów i klas abstrakcyjnych. Dla przykładu:

https://docs.oracle.com/javase/8/docs/api/java/util/AbstractMap.html

This class provides a skeletal implementation of the Map interface, to minimize the effort required to implement this interface.

https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html

Lists (and arrays) of objects that implement this interface can be sorted automatically by Collections.sort (and Arrays.sort). Objects that implement this interface can be used as keys in a sorted map or as elements in a sorted set, without the need to specify a comparator.

Gdyby nie było AbstractMap to każdą implementację Mapy trzeba byłoby klepać od zera, a to spowodowałoby duplikację kodu.
Gdyby nie było interfejsu Comparable to nie można by było stworzyć jednej metody, która sortuje listy zawierające dowolne elementy implementujące interfejs Comparable. Każdy musiałby klepać nową metodę sortującą dla każdej nowej klasy.

Podobnych przykładów w bibliotece standardowej Javy są tysiące. Poprzeglądaj kod tej biblioteki. Zobacz na klasy abstrakcyjne i inne po nich dziedziczące i przekonaj się ile to oszczędza kodu. Zobacz na interfejsy i miejsca ich użycia i przekonaj się jak to uelastycznia kod - twórcy biblioteki standardowej w ogóle nie musieli wiedzieć o twoich klasach (implementujących przez nich stworzone interfejsy) byś mógł używać tejże biblioteki.

Ciężko o lepszy przykład na zastosowania interfejsów i klas abstrakcyjnych w Javie niż sama biblioteka standardowa Javy.

0

W sumie @PrezesiQ to co napisał @Wibowit to bardzo dobry przykład.
Wyobraź sobie że masz pulę wątków -> ExecutorService. Przekazujesz mu klase która implentuje interfejs Runnable. Dzięki zastosowaniu polimorfizmu możesz przekazac różne implementacje Runnable, a ExecutorService z każdą sobie poradzi. Bez interfejsów i polimorfizmu musiałbyś tworzyć implementacje puli wątków dla każdego typu zadnia...

3

@PrezesiQ: z grubsza chodzi o to:

Wyobraź sobie, ze masz klasę pojazd, np. Car. Ten samochód, jak to samochód ma silnik RegularEngine. Twój silnik może wystartować i się zatrzymać, wiec dodajesz do klasy RegularEngine metody start i stop.

W celu utworzenia samochodu musisz mu dostarczyć silnik, wiec tworzysz konstruktor, który przyjmuje silnik RegularEngine engine.

Po jakimś czasie dochodzisz do wniosku, ze niektóre samochody maja silnik Turbo, wiec tworzysz druga klasę TurboEngine.
Problem jest taki, ze nie możesz go przekazać do swojego samochodu, bo on oczekuje obiekty typu RegularEngine, a nie TurboEngine.

W tym miejscu możesz utworzyć interfejs Engine, który będzie miał metody start i stop. Zmieniasz sygnaturę konstruktora z RegularEngine engine na Engine engine. Dodajesz do swoich silników implelements Engine i w tym momencie możesz za równo jeden jak i drugi silnik przekazać do konstruktora Car. Obydwa silniki są typu Engine. Obydwa silniki możesz wystartować i zatrzymać. Samochód obchodzi tylko to żeby mi dostarczyć coś co spełnia kontrakt, coś co można wystartować i zatrzymać. Możesz utworzyć jeszcze 500 innych silników i samochód je łyknie tak długo jak długo będą implementować Engine.

Implementacja interfejsu to obietnica, ze dany obiekt ma pewna funkcjonalność. Swojego rodzaju kontrakt, który dana klasa podpisuje i deklaruje, ze posiada zachowanie silnika (bo implementujac interfejs musisz dodac metody interfejsu do klasy). W tym wypadku implementując Engine obiecujesz ze jakiś obiekt można wystartować i zatrzymać.

Co dzięki temu zyskujesz? Używając interfejsu możesz niemodyfikujac klasy Car zmieniać jej działanie przekazując różne implementacje silnika Engine. Zyskujesz elastyczność. Możesz utworzyć BrokenEngine. Wtedy jak samochód wywoła metodę start to nic się nie stanie ;)

Przykład trochę z d**y, ale może będzie Ci to łatwiej zrozumieć niż na wątkach i innych abstarakcyjnych rzeczach, w których pewnie nie masz pojęcia.

Interfejsy zaczynaja mieć więcej sensu w miarę wzrostu skali Twoich projektów i wprowadzenia konceptow typu Dependeny Injection, nauki wzorców projektowych, takich jak Strategia itd.

0
Desu napisał(a):

@PrezesiQ: z grubsza chodzi o to:

Wyobraź sobie, ze masz klasę pojazd, np. Car. Ten samochód, jak to samochód ma silnik RegularEngine. Twój silnik może wystartować i się zatrzymać, wiec dodajesz do klasy RegularEngine metody start i stop.

W celu utworzenia samochodu musisz mu dostarczyć silnik, wiec tworzysz konstruktor, który przyjmuje silnik RegularEngine engine.

Po jakimś czasie dochodzisz do wniosku, ze niektóre samochody maja silnik Turbo, wiec tworzysz druga klasę TurboEngine.
Problem jest taki, ze nie możesz go przekazać do swojego samochodu, bo on oczekuje obiekty typu RegularEngine, a nie TurboEngine.

W tym miejscu możesz utworzyć interfejs Engine, który będzie miał metody start i stop. Zmieniasz sygnaturę konstruktora z RegularEngine engine na Engine engine. Dodajesz do swoich silników implelements Engine i w tym momencie możesz za równo jeden jak i drugi silnik przekazać do konstruktora Car. Obydwa silniki są typu Engine. Obydwa silniki możesz wystartować i zatrzymać. Samochód obchodzi tylko to żeby mi dostarczyć coś co spełnia kontrakt, coś co można wystartować i zatrzymać. Możesz utworzyć jeszcze 500 innych silników i samochód je łyknie tak długo jak długo będą implementować Engine.

Implementacja interfejsu to obietnica, ze dany obiekt ma pewna funkcjonalność. Swojego rodzaju kontrakt, który dana klasa podpisuje i deklaruje, ze posiada zachowanie silnika (bo implementujac interfejs musisz dodac metody interfejsu do klasy). W tym wypadku implementując Engine obiecujesz ze jakiś obiekt można wystartować i zatrzymać.

Co dzięki temu zyskujesz? Używając interfejsu możesz niemodyfikujac klasy Car zmieniać jej działanie przekazując różne implementacje silnika Engine. Zyskujesz elastyczność. Możesz utworzyć BrokenEngine. Wtedy jak samochód wywoła metodę start to nic się nie stanie ;)

Przykład trochę z d**y, ale może będzie Ci to łatwiej zrozumieć niż na wątkach i innych abstarakcyjnych rzeczach, w których pewnie nie masz pojęcia.

Interfejsy zaczynaja mieć więcej sensu w miarę wzrostu skali Twoich projektów i wprowadzenia konceptow typu Dependeny Injection, nauki wzorców projektowych, takich jak Strategia itd.

Hmmm, bardzo ciekawy przykład! To, że trzeba wszystkie funkcje zaimplementować wiem, choćby z implementowania interfejsów typu MouseListener, ActionListener itd. Ale jeszcze wtedy wiedziałem tylko tyle, że to taki magiczny druczek, bez którego program, nie zadziała. A te koncepty to jak na razie dla mnie jakiś chiński kosmos, pierwszy raz o tym słyszę xD

1

Poczytaj o tym co to jest polimorfizm, popatrz na kilka przykładów. Porównaj kod, który używa polimorfizmu i taki, który tego nie robi. Jak to zrozumiesz to i interfejsy zaczną mieć więcej sensu. Jest to podstawa podstaw, niezbędna do zrozumienia tego, o co pytasz :)

https://refactoring.guru/replace-conditional-with-polymorphism

@PrezesiQ jeszcze taki przykład z życia wzięty:

Wyobraź sobie ze chcesz zatrudnić hydraulika, żeby Ci coś naprawił. Nie wiesz kto do Ciebie przyjdzie i Cię to specjalnie nie obchodzi, ale wiesz co ten hydraulik powinien umieć zrobić, wiec definiujesz sobie listę zadań które hydraulik powinien być w stanie wykonać (pewnie piszesz to w ogłoszeniu). Każdy z hydraulików może mieć swój własny sposób na naprawienie kranu i to jak oni to zrobią nie ma dla Ciebie znaczenia ważne jest żeby koniec końców Twój kran działał.

W tym wypadku ta lista zadań to interfejs, a każdy z hydraulików, którzy potencjalnie do Ciebie się zgłoszą to klasy implementujace interfejs hydraulik.

W Javie mógłbyś mieć interfejs Plumber i klasy:
RegularPlumber implements Plumber
LysyZBrazzers implements Plumber
itd. :P

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