Interfejsy, kl abstrakcyjne - po co właściwie je stosować?

1

Witam
Na poczatku powiem, że nie wiedziałem czy zalozyc wątek w newbie czy tutaj, ale zdecydowalem o javie ze wzgledu na przyklad ktory podam zaraz.

Dzis staje przed wami z takim właśnie problemem. Do czego służa nam interfejsy i klasy abstrakcyjne w javie? wiem na czym to polega mniej-wiecej, ze klasy abstrakcyjne maja metody bez "ciała" i trzeba je nadpisac itd. Chodzi mi jednak o samo sedno, kiedy wy - jako bardziej zaawansowani je stosujecie? W jakich wypadkach trzeba pisac interfejs a w jakich nie? kiedy interfejs a kiedy klase abstrakcyjną, a kiedy w ogole zwykla klase?

Obecnie się ucze i w moich projektach jeszcze nigdy nie pisalem klasy abstrakcyjnej badz interfejsu, nie wpadłem na myśl że akurat gdzieś tam powinienem ją/go zrobić. Moze to byc spowodowane tym ze to małe programy, nie wielkie kolosy i może dopiero wtedy staje sie to przydatne? Może to sie zyskuje z doświadczeniem? (pytanie jak na to wpaść z doswiadczenia skoro nigdy sie tego nie używało.. ;) )
Chciałbym wiedziec kiedy to stosować :)

Przykład o którym wspominałem na poczatku: mamy interfejs ActionListener
Moje pytanie brzmi: dlaczego to akurat interfejs a nie zwykły obiekt? przeciez możnaby wykonywac operacje na obiekcie i tak z dystansem na to patrzac mogloby byc wygodniej, nie trzeba byloby importowac, napisywac metod, wszystko opieraloby sie o zwykłą klase.

Pytanie moje dlaczego tak zrobili a nie inaczej, czemu akurat interfejs a nie kl. abstr. albo zwykła klasa?

pozdrawiam i prosze o wsparcie w temacie :D

0

Klasy Abstrakcyjne (trochę inny język, ale idea ta sama)

0

Wybrałeś zły przykład, interfejs ActionListener ma tylko jedną metodę i ciało tej metody musi napisać programista - tylko on wie jak ma wyglądać obsługa zdarzenia. Zmiana na klasę lub klasę abstrakcyjna nie dałaby żadnych korzyści programiście. Dla interfejsów, które mają więcej metod (np. MouseListener) istnieje klasa abstrakcyjna z pustymi metodami. Programista może się ograniczyć do nadpisania tylko potrzebnych mu metod.

0

Interfejsy wydają się głupie:
Po co definiować, jakie metody będzie miała klasa? O ile wygodniej napisać samą klasę i po prostu z niej korzystać?
W praktyce robi się jednak trochę ciekawiej.
Wyobraź sobie, że masz interfejs ObslugaBazyDanych, a w nim metody "dodajUzytkownika(Uzytkownik) i usunUzytkownik(Uzytkownik).
Teraz piszesz sobie klasy: ObslugaBazyDanychWPliku implements ObslugaBazyDanych, ObslugaBazySQL implements ObslugaBazyDanych, ObslugaObiektowejBazyDanych implements ObslugaBazyDanych.
Po pierwsze:
Jak ktoś kiedyś będzie chciał napisać kolejną wersję obsługi z bazą danych, to od razu narzucisz mu standard, który musi spełnić.
Po drugie, możesz w dowolnym miejscu w kodzie zrobić:
ObslugaBazyDanych obd = new <któraś obsługa>();
obd.dodajUzytkownika(...);
Niezależnie od tego, którą obsługę wybierzesz (np. w jakiejś fabryce lub zależnie od parametrów użytkownika), to kompilator spokojnie to przepuści i będzie to śmigać.
Jest jeszcze weselej, jak dzielisz kod pomiędzy serwer i klienta (EJB, RMI). Wtedy obie strony muszą wiedzieć, jakie metody można wykonać, ale tylko jedna (serwer) ma ciało tych metod.

0

Ja to rozumiem w ten sposób ze nie ma sensu pisać nowych klas jak można wykorzystać interfejsy, ktore już istnieją. Do tego w Javie mozna dziedziczyć po jednej klasie a interfejsów możesz zaimplementowac dowolną liczbę wiec zamiast 7 klas można zrobic jedna implementujaca 6 interfejsów. Mam nadzieje ze moj tok rozumowania jest dobry:)

Napisane z HTC.

0

@PanKalmar nie, nie jest. To jest bardzo zła praktyka takie wpychanie interfejsów na pałę. Klasy "człowie-orkiestra" są złe, niemożliwe w utrzymaniu i wskazują na błędy projektowe. Klasy powinny być małe i zgodne z Zasadą Jednej Odpowiedzialności.
Idea interfejsów jest taka, żeby można było użyć nowej implementacji w starym kodzie. Tzn w kodzie który powstał przed tą implementacją. Poza tym ułatwia to też składanie implementacji z "klocków" poprzez delegację.

0

Wybrałeś zły przykład, interfejs ActionListener ma tylko jedną metodę i ciało tej metody musi napisać programista

A jaki może być dobry przykład? Taki który mi pokaże, że warto było użyć interfejs a nie zwykly obiekt?

Powoli zaczynam rozumieć, chodzi o to, że interfejs jest pewnym sprawdzonym schematem, który jest możliwy do wykorzystania dla wielu przypadków. Stąd też wyciągam wniosek, że interfejsy i klasy abstrakcyjne zaczyna się używać dopiero w większych projektach - bo w mniejszych programach nawet na siłe nie ma gdzie tego wcisnąć. Mam racje?

Bo o ile przykłady typu: mamy kota psa i mysz, maja podobne cechy tj. chodzenie, ilosc łap itp. i stworzymy superklase abstrakcyjną Zwierze - jest przykładem do pojęcia w zupełności - jednak w przypadku kiedy stajemy przed napisaniem programu gdzie juz nie chodzi o psy i myszy, a o zdecydowanie bardziej "informatyczne" tematy, wszystko zaczyna sie "capciać".

Wiec pytanie - czy poukładanie tego w głowie przychodzi z czasem? bo skoro obecnie nie używam - przez wielkość projektów bądz tez po prostu przez zbyt małą wiedzę - dlaczego miałbym kiedyś używać skoro wczesniej w ogole z tym nie praktykowalem?

dzieki za tyle odpowiedzi ;)

#edit

Idea interfejsów jest taka, żeby można było użyć nowej implementacji w starym kodzie. Tzn w kodzie który powstał przed tą implementacją. Poza tym ułatwia to też składanie implementacji z "klocków" poprzez delegację.

Gdybys mogl troche rozszerzyć myśl byłbym wdzięczny, co masz na myśli mówiąc o używaniu nowej implementacji w starym kodzie? (jeśli ktoś wyciaga projekt z przed 5 lat i ma tam interfejs X to może po latach napisać nową klase używajac tego interfejsu i implementując metody na nowy, bardziej nowoczesny czy też mądrzejszy sposób - bo a nóż pojawiły się nowe technologie --- o to tu chodzi?)

0
azalut napisał(a):

dlaczego miałbym kiedyś używać skoro wczesniej w ogole z tym nie praktykowalem?

bo kolega z teamu 2 tygodnie wcześniej napisze kontroler który będzie zarządzał obiektami implementującymi wymyślony przez niego interfejs a ty będziesz musiał te obiekty dorobić. Dlatego będziesz ich używał mimo braku wcześniejszej praktyki :)

0

tylko chodzi mi raczej o TWORZENIE przeze mnie wszystkiego od podstaw, aniżeli kontynuowanie czyichś dzieł.
Inaczej mówiąc chodzi mi o to, że ja bede nie tym który dorabia obiekty, a tym kolegą z teamu. Ja to napoczne - i czy bede wiedział, że należało zrobić interfejs? bo skoro nigdy nie tworzylem ich to zapewne nie wpadne na to że akurat to bedzie dobre rozwiązanie

Podaje tylko przykład wyżej, bo obecnie w żadnym teamie nie jestem, jestem jeszcze przez jakiś czas młodym niedoświadczonym samoukiem ;)

0

Ćwiczenie dla ciebie:

  • wymyśl sobie jakiś format wyjściowy pliku, najlepiej mega skomplikowany.
  • wymyśl sobie 3 formaty wejściowe
    Spróbuj napisać program konwertujący te 3 formaty wejściowe na format wyjściowy.
    Załóż że przewidujesz pojawienie się nowych typów plików wejściowych do obsłużenia.
    Sensowne rozwiązanie tego problemu wyglądałoby tak że zrobiłbyś jeden konwerter do postaci wyjściowej a następnie hierarchię klas o wspólnym zestawie metod, które będą "źródłem" dla konwertera. W efekcie konwerter będzie uniwersalny dla dowolnego formatu wejściowego o ile obiekt obsługujący wczytanie danych będzie udostepniał odpowiednie metody.
0

Najpierw trzeba się zastanowić czym jest interfejs i wtedy odpowiedź nasuwa się sama. Otóż interfejs służy do deklarowania metod, które muszą być zdefiniowane w obiekcie implementującym ten interferjs. Wiadomo też, że jeśli klasa implementuje interfejs, to możemy tej klasy użyć w metodzie, która jako argument pobiera typ będący tym interfejsem. Tutaj interfejs występuje w roli typu.

O co chodzi: mamy Klasy "KlasaA", "KlasaB" i interfejs "InterfejsA. "KlasaA" implementuje "InterfejsA" :

public class KlasaA implements InterfejsA{
  public KlasaA() {}

  public void zrobCos(){
     System.out.println(2);
  }
}

public class KlasaB {
  public KlasaB(){}

  public void uzyj(InterfejsA inter){
      inter.zrobCos();
  }
}

public interface InterfejsA {
  public void zrobCos();
}

Teraz mając gdzieś kod kliencki możemy napisać:

public static void main(String [] args) {
  KlasaA a = new KlasaA();
  KlasaB b = new KlasaB();
  b.uzyj(a);
}

Do czego to się przydaje w praktyce, i kiedy tego używać:
Przede wszystkim, ważne jest to - że faktycznie zamiast interfejsów można używać klas - można używać klas anonimowych, różne są sposoby którymi można zastąpić interfejsy, ale po co? W powyższym przykładzie aby użyć wyłącznie klas musiałbyś stworzyć trzecią klasę, powiedzmy "KlasaC", a w metodzie "uzyj" klasy "KlasaB" musiałbyś użyć jako argumentu - typu "KlasaC" - to działałoby równie dobrze.

I tutaj pojawia się jeszcze jedna sprawa, są sytuacje w których kod metody "zrobCos" jest zawsze inny. W tym przypadku nie ma sensu pisać klasy. Znacznie lepiej i wygodniej jest użyć interfejsu i implementować go w każdej klasie, której będziemy chcieli użyć w innym miejscu.

Właśnie ActionListener jest idealnym przykładem właściwego wykorzystania interfejsów. W metodzie, którą deklaruje ActionListener - Ty jako programista wymieniasz to, co ma się stać w momencie wykonania kliknięcia czy innej akcji. Możesz tego interfejsu użyć dla wielu swoich klas, które potem możesz przesłać do Componentu Swinga przez addActionListener() (czy jakoś tak, nie pamiętam dokładnie Swinga :).

Najważniejszą korzyścią jest to, że nie musisz tworzyć nowej klasy, która implementowałaby zachowanie po kliknięciu, lecz implementujesz to zachowanie w istniejącej klasie - metodą, której wymaga interfejs. Zasadniczo ta metoda to fragment kodu, który chcesz, żeby się wykonał po kliknięciu. Ułatwia to kilka spraw, bo:

  • Używając powyższego przykładu - KlasaC nie mając dostępu do danych KlasyB nie ma możliwości interakcji z nią.
  • Aby umożliwić klasieC dostęp do KlasyB, musiałbyś w niej zawrzeć referencję do niej i wywoływać wszelkie jej metody... a to jest problem, bo też KlasaC nie ma dostępu do prywatnych i zabezbieczonych metod i pól KlasyB.

Bardzo ładnie można to wytłumaczyć poprzez analogię do funkcji anonimowych w programowaniu funkcjonalnym np: JavaScript.
W JS można przesłać funkcję anonimową (lub referencję do funkcji) do innej funkcji. Poniższy kod:


function doFoo(funcBar) {
  funcBar();
}

doFoo(
  function() { alert("2")}
)

doFoo(function() { alert("5")}

... wyświetli "2" i potem "5".

Podobny efekt można użyskać w Javie, z użyciem interfejsów. W moim przykładzie, przesyłając obiekt KlasyA do obiektu KlasyC - w konsoli zostanie wypisane "2".

Jest jedna bardzo ważna zasada: dwie klasy implementujące ten sam interfejs nie mogą tego robić tak samo. Chodzi o to, że metody zrobCos obu klas muszą być różne bo wtedy można wydzielić ten kod do klasy nadrzędnej. Wyjątkiem mogą być jakieś banalne implementacje.

Zasadniczo interfejs DEFINIUJE ktoś kto tworzy bibliotekę - który będzie używany w kodzie klienckim (korzystającym z tej biblioteki).

Jeśli piszesz aplikację pod konkretne biblioteki to raczej mało prawdopodobne, że będziesz tworzył interfejsy, będziesz je raczej IMPLEMENTOWAŁ. Wiem to z własnego doświadczenia: tworząc silnik 3d miałem właśnie do czynienia z nasłuchiwaniem zdarzeń i wyglądało to bardzo podobnie do tego jak stworzony jest Swing. Przed tym też nie rozumiałem zastosowania interfejsów.

0

Zwykle interfejs robi się po to, by opisać za jego pomocą pożądane cechy obiektów, które przyjmujemy. Klasę abstrakcyjną pisze się, gdy tworzymy szkielet jakiejś pojedynczej funkcjonalności - czyli coś niepełnego ale też niepustego jeśli chodzi o funkcjonalność. Oczywiście można by olać interfejsy i klasy abstrakcyjne i obejść się bez nich, pozwalając tworzyć obiekty które są wadliwe lub nieprzydatne - gdyby było wielodziedziczenie klas w Javie oczywiście (a jest tylko dla interfejsów).

Innym zastosowaniem interfejsów jest: http://en.wikipedia.org/wiki/Interface_segregation_principle Dobieranie się do szczegółów implementacyjnych powoduje silne sprzężenie, które hamuje rozwój systemu, bo tych szczegółów implementacyjnych już nie można zmienić.

Poza tym, pisałem już wiele razy na forum o interfejsach :P Trzeba poszukać gdzie.

0

Stosowanie interfejsów w połączeniu z tworzeniem używanych przez nie obiektów przez fabryki zwracające referencje na interfejs, lub wstrzykiwaniem ich przez konstruktor pozwala uniezależnić implementowaną klasę od implementacji klas przez nią używanych.

Załóżmy, że stosujemy polimorfizm używając referencji na klasę abstrakcyjną, zawierającą fragment wspólnej implementacji:

  • Co jeśli postanowimy dodać nowy typ, który nie ma zawierać tego fragmentu implementacji? Może to być chociażby adapter na jakąś klasę mającą już zaimplementowaną taką funkcjonalność.
  • Co jeśli postanowimy, że nowa implementacja ma nie opierać się już na klasie abstrakcyjnej, a np. na klasie nieabstrakcyjnej używającej wzorca strategii, albo jakimś gotowym nieobiektowym kodzie?
  • Co jeśli Przy tworzeniu nowej implementacji nie używającej starej klasy abstrakcyjnej postanowimy, aby przejściowo używać niektórych typów obiektów ze starej implementacji a niektórych z nowej?

Jeśli nie stosujemy polimorfizmu, to możemy zacząć chociażby w celu stworzenia implementacji na potrzeby testów.

0

Czytałem wasze wypowiedzi 10 krotnie, i:
@Shalom

Ćwiczenie dla ciebie:

  • wymyśl sobie jakiś format wyjściowy pliku, najlepiej mega skomplikowany.
  • wymyśl sobie 3 formaty wejściowe
    Spróbuj napisać program konwertujący te 3 formaty wejściowe na format wyjściowy.

Tzn.. mam napisać swój własny format pliku, dosłownie? prawde mówiąc nie wiem jak sie do tego zabrać :D czy raczej taki pseudo-format, gdzie są wykonywane jakies obliczenia (tylko jakie ^^)?

@lukasz1985
Hm.. pisząc klase A chyba zawarłeś metode w konstruktorze, zastanawiam sie czy to celowo czy przypadkiem :)

Możesz tego interfejsu użyć dla wielu swoich klas, które potem możesz przesłać do Componentu Swinga przez addActionListener()

Właśnie.. wczesniej zauwazylem ze actionlistener działa dla wielu komponentów swinga, zarówno JFrame, JDialog czy też JButton, JMenuItem itd. to też jest jakas cecha interfejsu prawda? że wiele niby podobnych ale różnych klas które potrzebują tego interfejsu do poprawnego działania - i przeciez metoda actionPerformed jest zawsze inna w zależności co ma robić. (tak vice versa zastanawia mnie jak zrobili ten interfejs ze ma taka fajna funkcje jak sluchanie zdarzeń mimo, że jest w niej pusta metoda :D)

Musze sprawdzić w IDE, tylko za bardzo nie wiem jak - jesli miałbym np. jakąś klase GUI, jakiś JButton i chciał dodać do niego actionlistenera, tylko nie chciałbym w klasie GUI implementować actionlistenera bo klasa sie robi dłuuga od tych metod i mało czytelna - czy moge zrobić tak ze stworze klase X, zaimplementuje w niej actionListenera i wtedy jakoś tak zrobić, że w Jbutton w GUI bedzie łapał zdarzenia i jakos przekazać tą klase X jako argument do słuchania zdarzen? inaczej mówiąc - zeby logika tego actionlsitenera(obliczenia to co ma sie dać po wykonaniu zdarzenia) było zapisane w innej klasie - dla czystości kodu :)

Generalnie przykład który podałeś wydaje się całkiem logiczny - jednak nie wpadłbym na to pisząc jakiś swój mały program, obecnie po prostu nie widze dla tego użyteczność jakby i tak myśle jak zacząć ją zauważać, hmm..

Zasadniczo interfejs DEFINIUJE ktoś kto tworzy bibliotekę - który będzie używany w kodzie klienckim (korzystającym z tej biblioteki).

Jeśli piszesz aplikację pod konkretne biblioteki to raczej mało prawdopodobne, że będziesz tworzył interfejsy, będziesz je raczej IMPLEMENTOWAŁ. Wiem to z własnego doświadczenia: tworząc silnik 3d miałem właśnie do czynienia z nasłuchiwaniem zdarzeń i wyglądało to bardzo podobnie do tego jak stworzony jest Swing. Przed tym też nie rozumiałem zastosowania interfejsów.

Tak sobie właśnie myślałem - obecnie pisze programy które "coś robią" i używam czyjegoś API/bibliotek. W głowie jakos mi sie poukładało tak przez czas nauki, że programowanie można podzielić na taką część logiczna - tworzenie właśnie API/bilbiotek i taką jak to zwą "kliencką" czyli pisanie GUI programu i tworzenie tej głównej "perełki" która działa ;) nie wiem czy dobrze myśle

@Wibowit

Poza tym, pisałem już wiele razy na forum o interfejsach :P Trzeba poszukać gdzie.

Poszukam na pewno :P

0

@azalut lol no wymyśl sobie że w pliku wyjściowym chcesz mieć wszystko ustawione w kolumny na przykład, a w plikach wejściowych dane są ułożone inaczej (np. kolumnami a nie wierszami) etc.

0

Tak, zgadza się, pomyliłem się w konstruktorze wpisując tam metodę. Ale już poprawiłem.

"Właśnie.. wczesniej zauwazylem ze actionlistener działa dla wielu komponentów swinga, zarówno JFrame, JDialog czy też JButton, JMenuItem itd. to też jest jakas cecha interfejsu prawda? że wiele niby podobnych ale różnych klas które potrzebują tego interfejsu do poprawnego działania - i przeciez metoda actionPerformed jest zawsze inna w zależności co ma robić. (tak vice versa zastanawia mnie jak zrobili ten interfejs ze ma taka fajna funkcje jak sluchanie zdarzeń mimo, że jest w niej pusta metoda :D)"

Strasznie mieszasz... A sprawa jest prosta. Ty w swojej własnej klasie robisz:

class MojaKlasa implements ActionListener

w której MUSISZ zaimplementować metodę actionPerformed (jeśli tego nie zrobisz jest compile error), np:

public void actionPerformed(){
  System out println("action performed") 
}

potem gdzieś tworzysz instancję tej swojej klasy:

MojaKlasa mojaKlasa = new MojaKlasa();

i przesyłasz tą instancję do komponentu Swinga (JButton lub inny).

"jakisTamKomponent.addActionListener(mojaKlasa)"

I tutaj jest koniec Twojej pracy z interfejsem.

Możesz się zapytać: a dlaczego przesyłasz instancje swojej klasy?
Bo sygnatura addActionListener to "addActionListener(ActionListener)"
Czyli musisz przesłać obiekt typu ActionListener.

Czy Twoja klasa jest typu ActionListener? Tak bo implementuje interfejs ActionListener (i co za tym idzie metodę "actionPerformed").
Zauważ, że możesz zaimplementować w swojej klasie kilka interfejsów, poza ActionListener, mógłbyś zaimplementować interfejs BlaBlaBleee.

Chodzi o to, że teraz Twoja klasa ma 3 typy: "MojaKlasa", "ActionListener" i "BlaBlaBlee", których możesz użyć, przesyłając obiekt do innych metod.
W przypadku Swinga do metody addActionListener.

Swing już natomiast będzie sobie tam pracował z Twoim obiektem jako typem "ActionListener" i kiedy coś się wydarzy (kliknięcie, czy coś) po prostu wywoła metodę "actionPerformed()".

Musze sprawdzić w IDE, tylko za bardzo nie wiem jak - jesli miałbym np. jakąś klase GUI, jakiś JButton i chciał dodać do niego actionlistenera, tylko nie chciałbym w klasie GUI implementować actionlistenera bo klasa sie robi dłuuga od tych metod i mało czytelna - czy moge zrobić tak ze stworze klase X, zaimplementuje w niej actionListenera i wtedy jakoś tak zrobić, że w Jbutton w GUI bedzie łapał zdarzenia i jakos przekazać tą klase X jako argument do słuchania zdarzen? inaczej mówiąc - zeby logika tego actionlsitenera(obliczenia to co ma sie dać po wykonaniu zdarzenia) było zapisane w innej klasie - dla czystości kodu :)

Oczywiście, że tak można, ale weź pod uwagę kilka spraw:

Załóżmy że masz tę klase X i masz tam jakies pola, np "private int foo;" i "private String bar;"

Zapytaj się teraz siebie, czy gdy zdarzenie wystąpi - czy będziesz potrzebował tych wartości, na przykład, żeby je zmienić?

Jeśli będziesz miał je w klasie GUI - to jeśli "przeniesiesz" interfejs ActionListener do tej nowej klasy X - to nie będziesz miał w niej dostępu do tych wartości, które zostały w klasie GUI.

Kolejną sprawą jest to, że jeśli tworzysz kolejną klasę, tylko po to, żeby zaimplementować w niej interfejs actionListener i wpisać jedynie metodę "actionPerformed" to interfejs traci swój sens i wracamy do punktu, gdzie wystarczą tylko klasy.

0

Najprostszy przykład to program z systemem pluginów.

Każdy plugin musi się dać załadować, wyświetlić, wyświetlić opcje, wykonać akcje.

Skoro plugin zostanie załadowany z (najprawdopodobniej) zewnętrznego pliku dll i będzie robiony przez kogoś spoza firmy to musi wiedzieć jak ma taki plugin napisać. Program też musi jakoś obsługiwać pluginy.

Dlatego robi się interfejs - plugin musi być utworzony na bazie interfejsu i wtedy program główny może go spokojnie obsłużyć wywołując metody interfejsu.

0

Ahh skasować posta którego sie pisało 15 minut, to jest to... od nowa:
@lukasz1985

Chodzi o to, że teraz Twoja klasa ma 3 typy: "MojaKlasa", "ActionListener" i "BlaBlaBlee", których możesz użyć, przesyłając obiekt do innych metod.

Hmm nom racja, wtedy klasa ma coraz wiecej typów (czy to sie tyczy polimorfizmu własnie?) i mówisz ze to kiedyś na prawde sie przyda, tzn ze wpadne na pomysł, że gdzieś faktycznie można tego użyc? w jakichs wiekszych aplikacjach czy cus :P (wiadomo mi jednak, ze klasa nie moze miec tez wcisniete max tych interfejsów bo jest niezgodne z zasada pojedynczej odpowiedzialnosci wtedy i jest takie wszystko w jednym)

Jeśli chodzi o tą metode actionPerformed itd. to: załóżmy ze chce zeby program wykonywal screena na przycisk JButton i jak nacisne klawisz "a" również. Potrzebuje więc importować ActionListenera i KeyListenera. Jako ze w aplikacji mam pare innych buttonów, mam 3 wyjscia:

  1. zrobic klasy anonimowe dla kazdego zdarzenia i buttona osobno (troche gmatwa kod, malo czytelny sie robi, bo klasa sie dłuuuzy i dłuzy)
  2. zaimportowac w gui oba interfejsy i nadpisc metody i w nich lapac zrodlo zdarzenia i wywoływać daną akcje
  3. zrobic osobne klasy: zaimportować w nich w jednej actionperformed, w drugiej keylistener i wtedy w nich dac referencje do GUI i sprawdzać źródło zdarzenia (za pomoca jakiegos gettera z GUI) i wywolywac zdarzenia
    Ten trzeci pomysl powstal z tego, ze pierwsze dwa strasznie wydłużaja kod klasy odpowiedzialnej za UI. Wtedy nie jest ona tylko od tworzenia wygladu programu, a ma w sobie też logike dla przycisków itd, wtedy staje sie mało "czysto pisana" i baardzo długa (a w wypadku kiedy Jbuttonów czy JMenuItem'ów itd mamy >10 to dopiero sie zaczyna)
    Nie wiem co o tym myślicie, moze jakies inne wyjście jest? :D

@Ola Nordmann
Powiem ci ze przykład bardzo dobry, mozna pisac wiele pluginów, nie zawsze pisze ta sama osoba - a w kodzie musi panować jakiś układ zeby wszystko ze soba współgrało jak należy - i od tego pewnie sa interfejsy, zgadza sie? dużo dał mi twoj post :P

0

Od tego i od wielu, wielu innych rzeczy :).

0

@azalut
To zależy jeśli chodzi o punkt 3 - jeśli masz wydzielać interfejs do klasy po to tylko, żeby w niej zaimplementować jego metody to nie ma sensu - wtedy zrób to według punktu 2, jeśli jednak masz jakieś dane, na których ta nowa klasa będzie pracować samodzielnie - wtedy tak - punkt 3 jest dobrym rozwiązaniem, jednak pozostaje problem komunikacji z klasą główną.

Tak jak piszesz - będziesz musiał przechowywać referencje do klasy GUI i wywoływać jej metody. To jest dobre rozwiązanie ponieważ wydzielasz 1 system główny (klasa GUI) i 2 podsystemy (dla tych dwóch nowych klas). Podsystemy mogą się komunikować między sobą przez system główny - który staje się mediatorem w tej komunikacji.

Stąd też nazwa wzorca projektowego "mediator" :)

0

Ciekawe czy ktoś się nad tym zastanawiał co napisałem:

Innym zastosowaniem interfejsów jest: http://en.wikipedia.org/wiki/Interface_segregation_principle Dobieranie się do szczegółów implementacyjnych powoduje silne sprzężenie, które hamuje rozwój systemu, bo tych szczegółów implementacyjnych już nie można zmienić.

Dodam jeszcze, bo mi się przypomniało, że w Javie 8 interfejsy nabiorą nowego znaczenia, bo będą mogły zawierać implementacje metod ;] Nie będzie to typowe wielodziedziczenie, ale będzie mieć sporo wspólnego.

0

Jeszcze jeden przykład wykorzystania.
Kiedyś pisałem komunikator (sztuka dla sztuki) o eksperymentalnej architekturze. Sam program ograniczał się do wczytywania DLL'ek i zarządzania nimi. W DLL'kach obligatoryjnie musiało się znaleźć GUI, przynajmniej jedna 'wtyczka' do obsługi sieci komunikatora (jakiś Jabber, czy inne g**no). Do tego wtyczki ogólne w których można było zaimplementować radio, czy inne g***a. Każdy interfejs udostępniał metody, które aplikacja wywoływała w głównej pętli. W ten sposób po stworzeniu aplikacji mogłem niezależnie modyfikować każdą część. Oczywiście GUI udostępniało też obszar rysowania i jego wymiary, aby wtyczka mogła się narysować. Wtyczki sieciowe miały wyjątkowe przywileje jeśli o to chodzi. W tym przypadku interfejsy były tylko po to, żeby appka wiedziała jak się dobrać do ważnych dla niej rzeczy. Zresztą samo słowo interfejs pochodzi od angielskiego interface, co znaczy dosłownie międzytwarz. Można to rozumieć jako narzędzie do komunikacji - taka dokumentacja dla programu, żeby (jak w danym przykładzie) wiedział jak coś obsłużyć. Tworzenie bardziej szczegółowych interfejsów to katastroficzna głupota, która kończy się ograniczeniem rozwoju programu. Przynajmniej ja nie potrafię sobie wyobrazić sytuacji w której takie interfejsy były by przydatne.

0

Dlaczego szczegółowe interfejsy są złe? Problemem jest niedopasowanie interfejsu do warstwy w której będzie używany. Tyle.

Co do komentarza nt. nadpisywania pojedynczej klasy to zazwyczaj problemem nie jest nadpisanie, ale konieczność uwspólnienia korzenia w modelu. Jeżeli klasy posiadające ten sam interfejs muszą jeszcze dodatkowo dziedziczyć, po "domyślnej klasie abstrakcyjnej" to nie jest to najlepsze rozwiązanie. Szczególnie na dostarczenie domyślnej implementacji. Znacznie lepiej jest mieć coś w rodzaju Tailsa z domyślną implementacją, który będzie "przezroczysty" dla dziedziczenia niż zaszytą na sztywno klasę bazową.

Świetnym przykładem dlaczego tailopodobne rozwiązania są dobre jest stary Spring i dziedziczenie po kontrolerze. Z jednej strony trzeba dziedziczyć po klasie springowej, a z drugiej implementować intrefejs biznesowy. Interfejsy biznesowe są do siebie podobne, zatem fajnie jest stworzyć klasę abstrakcyjną i tu jest problem, bo "zbieżność" zadań w jednym miejscu powoduje, że zaczynamy budować własny framework pomiędzy springiem, a "biznesowym mięsem". Jeżeli teraz trafia się nietypowy element to nagle się okazuje, że "bez dziedziczenia po naszej klasie nie wykona się".

0

Dlaczego szczegółowe interfejsy są złe?

Dlatego, że przy rozwoju aplikacji trzeba wybierać między kompatybilnością wsteczną, a 'rezygnacją' z przestarzałych rozwiązań. Żeby nie były złe muszą być naprawdę dobrze przemyślane.

Jeżeli klasy posiadające ten sam interfejs muszą jeszcze dodatkowo dziedziczyć, po "domyślnej klasie abstrakcyjnej" to nie jest to najlepsze rozwiązanie.

W C++ mam wielodziedziczenie i tam dziedziczę tylko po klasie abstrakcyjnej. Metody czysto abstrakcyjne robią za interfejs. W javie to samo, chyba, że trzeba dziedziczyć z wielu interfejsów. Podałem jako przykład metodę logującą, bo to się raczej nie zmienia. Innym przykładem może być klasa abstrakcyjna CActor ze zdefiniowanymi metodami przemieszczania się na mapie (trasowania, nie renderowania), Akcesory dla HP etc. I na przykład mamy pijaka, u którego trasowanie wygląda inaczej. Dlaczego robić klasę abstrakcyjną, która będzie matką? To może być część jakiegoś silnika, albo projektu zespołowego i każdy wie co implementować. Choć oczywiście częściowo muszę przyznać Ci rację - wszystko zależy od projektu.

Co do przykładu - można zrobić jakiś adapter, ale interfejsy są właśnie po to, żeby nietypowy przypadek się nie pojawił. Ale ogólnie masz rację.

0

Klasy-adaptery zapewniające "pustą implementację" to chyba najgorsze z możliwych rozwiązań. Ni idzie z nich dziedziczyć, bo zazwyczaj chcemy po drodze mieć możliwość wykorzystania jakiejś funkcjonalności biznesowej, a z drugiej strony implementacja interfejsu w prost oznacza konieczność samodzielnego wygenerowania iluś tam metod, które będą miały puste ciało. Jak dla mnie masakra.

Dużo zależy od projektu, ale też wiele od tego jakie możliwości daje język/środowisko.

0

Dla jednego nietypowego przypadku, gdzie nie mamy dostępu do kodu adapter jest lepszym wyjściem niż pisanie kodu od nowa. Tym bardziej, że kompilator to zoptymalizuje :)

0

W kontekście "jednego nietypowego przypadku" to kiedyś taką bajkę napisałem:

Dawno, dawno temu na stercie żył sobie Obiekt Pewnej Klasy (OPK). OPK był bardzo smutny, gdyż nie miał kolegów z którymi mógłby się wymieniać danymi i bawić w gry synchronizacyjne. Dlatego też OPK prosił Programistę:

  • Programisto stwórz mi kolegę bym mógł z nim pracować i się bawić po pracy
    Programista widząc, że OPK chce dobra usłuchał jego prośby. Stworzył Kolejny OPK i go zainicjował po czym wrócił do do picia piwa i oglądania filmików.

OPK i KOPK dość szybko stwierdziły, że do brydża trzeba czterech, a potem już we czwórkę, że piłka kopana to co najmniej 22 uczestników. I tak oto O-tyPK mnożyły się i zapełniały RAM i zabierały procesor. Koniec końców, gdy skończyło się piwo, a filmiki z powodu braku zasobów komputera drastycznie zwolniły Programista zajrzał na stertę, zasmucił się i rzekł:

  • Dlaczego żeś mi to uczynił OPK?
    Na co OPK odpowiedział

  • Bo mnie nie przetestowałeś idioto…

  • Oż w żopu… ale i tak masz przestane, bo będziesz Singletonem :D - rzekł Programista i zabrał się do przerabiania OPK.
    Programista kodował całą noc, refaktoryzował kod i przebudowywał referencje. Jednak pamiętając, że obiekt smutny będzie, gdy nie będzie mógł grać w ukochaną synchronizację stworzył mu pola finalne by już nigdy nie pragnął synchronizacji. Po czym powołał OPK do życia i rzekł:

  • Za karę, że mnożyłeś się bez opamiętania uczyniłem Cię Singletonem. nigdy już nie będziesz pragnął grać w Synchronizację. Jeszcze Cię tylko przetestuję.
    OPK popatrzył na siebie i rozradowany rzekł:

  • A c**** se przetestuj mondziole… Mam pola final i private co raz utworzone nie może być zmienione więc mnie nie przetestujesz, bo konfigurację dostarczasz tylko raz w konstruktorze.
    To powiedziawszy OPK dumny z siebie zasiadł w statycznej pamięci.

http://koziolekweb.pl/2008/03/22/trzeba-miec-refleks-synku-o-testowaniu-singletonow/

Generalnie tego typu rozwiązania mają tendencję do mnożenia się przez pączkowanie.

0

No tak, ale to jest teoria pierwszej wybitej szyby. Nietypowe przypadki trzeba tępić w zarodku ;)

Jak już tak offtopujemy to niezła bajka - tylko singletone'ów nie trawię. Są to głównie klasy typu TextureManager. I nawet w projekcie zespołowym jeśli ktoś próbowałby mi taką klasę utworzyć w środku kodu to bym mu polecił popukać się czymś bardzo ciężkim w czoło.

0

Dlaczego? Znowuż, odrzucasz pewne rozwiązanie. Moim zdaniem całkowicie niesłusznie. Singleton jak jest prawidłowo używany to jest jednym z lepszych rozwiązań jakie istnieją.

0

Tylko nie wiem do czego to ma służyć. To tak jakby zrobić kod debiloodpornym. Nie miałem okazji pisać jakiegokolwiek projektu w grupie, ale próba utworzenia obiektu (który powinien być singletone) w środku kodu to dla mnie idiotyzm. Jakiemu obiektowi warto nadać singletone? Obiekty, które powinny być singletone powinny istnieć na przestrzeni całego programu, albo pewnego fragmentu, którego inicjalizacją i zwalnianiem tak czy inaczej powinna się zajmować jedna osoba. Także zabezpieczanie, przed podwójnym utworzeniem (po raz kolejny) nie ma dla mnie żadnego sensu.

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