Sens używania interfejsów

1

Cześć. Mam problem ze zrozumieniem samej idei interfejsów. Wiem jak one wyglądają, jak stosować i teoretycznie wiem co robią, ale wydają mi się one kompletnym bezsensem. Załóżmy mam klasę "kot" i kolejną "pies". Chcę, żeby w każdej z nich była metoda "biegaj()". No i spoko, zrobie interfejs, każda klasa będzie go implementować, ale jaki jest jego sens? Równie dobrze mogę w klasie kot napisać metodę "biegaj" i tak samo w klasie pies, co de facto z interfejsem też robię, no bo przecież te kasy go tylko implementują a ciało metody biegaj każda klasa będzie miała osobno. Więc bez zastosowania interfejsu też będzie wszystko działać. Czy byłby ktoś w stanie łopatologicznie wytłumaczyć mi co on nam daje, oprócz tego, że pokazje, jakie metody klasa musi mieć w sobie zaimplementowane? Dla mnie to jakiś komplety bezsens i niepotrzebne dodatkowe linie kodu... Byłbym wdzięczny, gdyby jakaś mądra głowa wbiła mi to do mojej, pustej:D

1

Interfejsy są po to, by operować na abstrakcjach. Załóżmy, że jest kilka rodzajów produktów, ale każdy rodzaj implementuje ogólny interfejs Item mający metodę getPrice(). Następnie mamy koszyk, który ma metodę getTotalPrice(), który iteruje po liście produktów i sumuje ceny. W innym miejscu metoda getPrice() mogłaby być wykorzystywana do sortowania i filtrowania produktów. Itp, itd...

0

Tez mam problem ze zrozumieniem w 100% sensu interfejsów. Dlaczego każda z tych klas produktów nie może mieć swojej metody getPrice() ? Co daje użycie interfejsu? Jaka jest różnica między użyciem interfejsu a bez niego?

2

Jeśli nie będzie wspólnej nadklasy/ interfejsu to wtedy będziesz miał List<Object>, a potem będziesz musiał sprawdzać typy, rzutować i dopiero wywoływać metody. Przy List<Item> możesz przeiterować po liście, na każdym elemencie wywołać getPrice(), zsumować i zwrócić jako getTotalPrice() w koszyku.

0

Każda z tych klas implementujących interfejs Item przecież ma swoją metodę getPrice(), to jaką zwróci wartość zależy od tego jaką cenę ma dany produkt. W zasadzie interfejsy wymuszają na Tobie dodanie odpowiednich pól i/lub metod (program Ci się nie skompiluje jeżeli Twoja klasa implementuje interfejs i nie dodałeś metod tego interfejsu do ciała tej klasy) przez co odciąża programistę od zbędnego myślenia i zmniejsza ryzyko wystąpienia błędów.

2

Wyobraź sobie, że masz taką sytuację:

public class Main {
    
    public static void main(String[] args) {
        X1 x1 = new X1();
        ...
        X100 x100 = new X100();

        x1.doSomething();
        ...
        x100.doSomething();
    }
}

W tym wypadku jesteś zmuszony wyprodukować około 100 linii kodu tylko po to, żeby wywołać dla każdego elementu xN metodę doSomething(), nawet, jeżeli każda klasa opisująca każdy z tych obiektów implementuje tą metodę w ten sam sposób.

Teraz spójrz na przykład z interfejsem:

public interface X {
    void doSomething();
}

// Klasy od X1 do X100 implementują interfejs X.

public class Main {

    public static void main(String[] args) {
        X[] objectsImplementingInterfaceX = new X[99];
        
        objectsImplementingInterfaceX[0] = new X1();
        ...
        objectsImplementingInterfaceX[99] = new X100();
        
        someMethod(objectsImplementingInterfaceX);
    }

    static void someMethod(X[] ob) {
        for (X x : ob) {
            x.doSomething();
        }
    }
}

Imlementacja określonego interfejsu przez wiele różnych klas pozwala Ci zebrać wszystkie referencje do tych obiektów do kupy i wywoływać dla nich masowo metody zdefiniowane przez ten interfejs, np. poprzez iterację typu for-each.

=======przerwa na ciasteczko (hit waniliowe)========

A teraz bardziej życiowy przykład. Wyobraź sobie sytuację, w której w jednym miejscu swojego programu masz coś takiego:

ArrayList<T> ref = new ArrayList<>();

Dalej w kodzie masz natomiast pierdyliard metod, które przyjmują ref jako argument (oczywiście typu ArrayList, żeby było ciekawiej). Jednak pewnego pięknego dnia wydarza się coś cudownego - doznajesz objawienia i stwierdzasz, że zastosowanie LinkedList zamiast ArrayList będzie dużo bardziej spoko. Otwierasz więc IDE i rozwalasz monitor, ponieważ czeka Cię przyjemność ręcznego refactoringu kilku tysięcy linijek kodu. "Ale zaraz, zaraz..." - mówisz - " Czy aby przypadkiem List nie jest interfejsem, który jest implementowany przez ArrayList i LinkedList?" Ze smutkiem stwierdzasz, że tak jest w istocie, przez co żeby uniknąć kłopotów w przyszłości piszesz:

List<T> ref = new LinkedList<>();

...a także zmieniasz typ parametrów metod z klasowego na interfejs. Przez co jeżeli w przyszłości najdzie Cię ochota, żeby skorzystać z klasy Vector zamiast LinkedList, to po prostu napiszesz w jednym miejscu swojego programu:

List<T> ref = new Vector<>();

...i voila.

<EDIT/UPDATE>
Jest jeszcze wiele fajnych zastosowań (w tym oparte na interfejsach funkcyjnych), ale ogarnij najpierw podstawy i ułóż sobie to wszystko w głowie :) Trzymam kciuki.

3

Istotą jest polimorfizm, np:

class StoperDlaZwierząt{
  Czas mierzCzas( Biegające b ){
    czas.start();
    b.biegaj();
    czas.stop();
    return czas;
  }
}

i wtedy metoda mierzCzas będzie mogła mierzyć czas każdemu zwierzakowi implementującemu interfejs Biegające, który ma metodę biegaj. Na przykład:
stoper.mierzCzas(pies);
stoper.mierzCzas(kot);
itd

To samo możesz osiągnąć przez dziedziczenie, ale dziedziczenie dodatkowo wprowadza hierarchię między klasami, interfejsy są bardziej "anemiczne". Na przykład jak zrobisz bazową klasę Zwierze z metodą biegaj, to może być głupio zrobić klasę Człowiek dziedziczącą po Zwierze. Jeśli użyjesz interfejsu- Człowiek może implementować taki interfejs. Inne typowe interfejsy to Map, Set, List itd, określają interfejs a implementacje/algorytmy mogą być różne.

0

Interfejs jest kontraktem. Określa dostępne metody. Innymi słowy, jeśli twoja klasa implementuje interfejs, czyli spełnia kontrakt, to używający wie, że Kot, Pies, czy inne zwierze implementuje metody zawarte w interfejsie i nie obchodzi go w jaki sposób ten interfejs jest implementowany.

PS: Właśnie zauważyłem, ze Krzywy Młot dobrze to opisał :)

0

Interfejs zmniejsza poziom zależności między klasami, ponieważ zwykle nie udostępnia niepotrzebnych śmieci (np. konstruktorów), dzięki czemu klasy te lepiej znoszą zmiany w wykorzystywanych klasach. Ułatwiają też napisanie wymaganej klasy od nowa, zwłaszcza jeśli klasa implementuje kilka interfejsów a nas obchodzi w danym momencie tylko jeden.

4

Ale kombinujecie z tymi wyjaśnieniami - "zapomniał wół jak cielęciem był". Kolego, to że wydaje Ci się to bez sensu na tym etapie nauki jest całkowicie normalne. Móże również zająć trochę czasu zanim w pełni zrozumiesz sens używania interfejsów. Ale mówiąc najprościej- masz klasę Kot i Pies. Napisz teraz metodę która weźmie jeden parametr i na rzecz tego parametru wywoła metodę Biegaj(). Podpowiedź: jako typ parametru użyj interfejs.

Jeśli mogę coś poradzić- pamiętaj że wszystko czego uczysz się na tym etapie to głównie nauka mechaniki. W wielu przypadkach nie będziesz widział sensu używania czegoś bo faktycznie w konkretnym przykładzie takiego sensu nie będzie. Dopiero z czasem kiedy człowiek ma styczność z bardziej skomplikowanym kodem pewne mechanizmy nabierają sensu. Sam dobrze pamiętam jak zaczynalem od podstaw. Na początku wszystko uczyliśmy się (na uczelni) pisać w metodzie Main(), kiedy przyszedł czas na metody i funkcje z początku nie widziałem w tym sensu- przecież mogę cały kod walnąć w Mainie i będzie dobrze. Kiedy w końcu pojąłem (a raczej zacząłem pisać więcej kodu) przyszedł czas na obiekty- i znów nie widziałem sensu, przecież mogę mieć tablice z imionami, wiekami itp. I znów po jakimś czasie zobaczyłem w tym sens. Z czasem to po prostu "zaskoczy", pamiętaj tylko że na tym etapie nauki czym bardziej coś wydaje Ci się bez sensu, tym większy jest tego sens ;) Pisz i jeszcze raz pisz, powodzenia.

0

http://www.javaworld.com/article/3044050/learn-java/discover-the-six-roles-that-interfaces-play-in-the-java-language.html

Interfejs mówi klasie w której tworzymy nasze zmienne, że klasy go implementujące są wymienne. Więc zamiast musieć robić osobną zmienną dla każdego zwierzęcia, możemy utworzyć zmienną typu Zwierzę - analogicznie jak przy dziedziczeniu z klasy abstrakcyjnej.
Interfejsy mogą też całkowicie zastępować dziedziczenie, pozbawiając nas wielu problemów z nim związanych.

0

Interfejs wymusza określone zachowania na klasie. Wszystkie metody interfejsu muszą mieć swoją implementacje w klasie (jeżeli nie korzystamy z default), która po nim dziedziczy.

0

Sens używania interfejsów można porównać do budowy i użytkowania samochodów. Do sterowania samochodem masz urządzenia takie jak kierownice, skrzynię biegów itp. To jest interfejs samochodu. Ty nie musisz znać implementacji tzn. nie masz zielonego pojęcia jaki jest silnik; czy elektryczny, czy na benzynę, na słońce, na paliwo jądrowe; tak samo nie wiesz jak jest zbudowana skrzynia biegów. Nagle zachodzi potrzeba wymiany silnika albo skrzyni biegów. Kierowca ma te same przyrządy do sterowania pojazdem, może nawet nie zauważy, że coś zostało zmienione.
W programowaniu masz analogiczną sytuacje. Jest interfejs i są implementacje. Wymieniasz implementacje, a interfejs zostaje ten sam. Nie musisz wiedzieć nawet, że zostało coś zmienione. To się fachowo nazywa polimorfizm.

0

Dziękuję Wam za wszystkie odpowiedzi. Niestety dalej nie mogę zrozumieć tego... Ja wiem, że interfejs jest tym, jak to zostało ładnie nazwane, "kontraktem", ale nie mogę zrozumieć po co to jest i wydaje mi się kompletnym bezsensem. Gjorni - javastart chyba z 5 razy przeczytałem i niestety ta lekcja im kompletnie nie wyszła bo jest całkowicie niezrozumiała (za mało szczegółów pewnie). Najbardziej trafia do mnie post kolegi Krzywy Młot bo jest to konkretny, prościutki przykład i powoli coś zaczynam z tego rozumiec (przynajmniej tak mi się wydaje). Może ktoś by zechciał jeszcze podobne przykłady z opisami podać?:) Rozumiem, że jeśli klasa xyz implementuje interfejs abc, to w metodzie, która wygląda tak: metoda(abc argument) mogę jako argument zastosować obiekt klasy xyz, tak?
Proszę was, nie piszcie, że interfejs mówi jak ma się coś zachowywać i zawiera tylko deklaracje metod, bo ja to wiem i własnie z tym mam problem, że wiem ale nie widze sensu:D Tak samo z samochodem - wymieniam implementacje a interfejs zostaje... ok, ale i tak muszę we wszystkich klasach te implementacje zmienić, więc to dla mnie jest właśnie niezrozumiałe - interfejs jest bo jest i nic nie daje (tak to widzę:D ). Może macie jeszcze jakieś fajne przykłady 'z życia' jak kolega Krzywy Młot? Swoją drogą świetne forum. Nie sądziłem, że od razu taką pomoc otrzymam. Dzięki!

0
  • Zamiast wielodziedziczenia masz interfejsy;

  • Obie rzeczy (abstract, interface) są oparte o dziedziczenie i polimorfizm;

  • Stosując interfejs wymuszasz w klasie odpowiednie zachowania, do których developer musi się dostosować chcąc w swojej klasie dziedziczyć od tego interfejsu (nie mówimy o default). Kiedy dobrze wszystko zaprojektujesz to możesz stosować luźne referencje przekazując np. jako argument metody interfejs, a developer ze swoją klasą musi się podporządkować i do tego dostosować. Pozwala to też na łatwą zmianę klas, które dziedziczą od interfejsu bez konieczności zmiany argumentu metody. Po prostu klasa ma się tak zachowywać i koniec.

  • Prosty przykład: interfejs Samochod z metodami Jedz, ZatrzymajSie, PrzerzucBieg. Po tym interfejsie może dziedziczyć Fiat czy Porsche ale jako argument metody możesz zastosować interfejs: public void Foo(Samochod samochod). Wtedy możesz skorzystać w ciele tej funkcji z metod Jedz, ZatrzymajSie, PrzerzucBieg, bo wiesz, że na pewno posiadają implementację.

1

Zacznijmy od tego czy wiesz co to jest polimorfizm? Miałeś styczność z programowaniem generycznym w stylu List<String>? Jeśli nie, to ciężko ci będzie zrozumieć praktyczny sens interfejsów, możesz albo się doszkolić z tych tematów albo przyjąć to tymczasowo na wiarę. Owszem, musisz tak czy tak tę implementację napisać ręcznie dla każdej klasy, ALE: klasy które korzystają potem z twojego kodu mają pewność, że takie metody tam są i dzięki temu mogą je wywoływać. Nie możesz od tak sobie zrobić zmiennej typu Object i wołać na niej metody szczekaj(), kompilator musi być pewien że pod tą zmienną jest obiekt klasy która posiada taką metodę.

0

@marcin43210 najprosciej będzie jak sam dojdziesz do tego po co są interfejsy. Napisz proszę uniwersalną funkcje min która dla podanych 2 obiektów zwraca ten który jest mniejszy.

2

Myślę, że najprościej zrozumieć to poprzez wzorzec projektowy "Strategia".

Mamy interfejs:

public interface Car {
          void nitro();
}

Klasy:

public class Volvo implements Car {
       public void nitro() {
             System.out.println("Volvo likes Nitro");
      }
}
public class Fiat implements Car {
       public void nitro() {
             System.out.println("Fiat likes Nitro");
      }
}

I teraz np. klasa kierowcy:

public class Driver {
  
       private Car car;

       public Driver(Car car) {
            this.car = car;
       }

       public void timeToNitro() {
             car.nitro();
      }
}

Do klasy kierowcy przekazujemy intefejs Car zamiast konkretnej klasy, dzięki czemu kompilator wie, że może wywołać metodę nitro, ponieważ znajduje się w tym interfejsie, a my możemy mieć 100 innych samochodów i każdy może zostać przekazany do konstruktora klasy Driver:

public statoic void main(String... args) {
      Car volvo = new Volvo();
      Driver driver = new Driver(volvo);
      driver.timeToNitro(); // output: "Volvo likes Nitro"
}
1
public interface Zwierze {

    public String wydajOdglos();
}

public class Pies implements Zwierze {

    @Override
    public String wydajOdglos() {
        this.szczekaj();
    }
}

public class Kot implements Zwierze {

    @Override
    public String wydajOdglos() {
        this.miaucz();
    }
}

public class Main {

    public static void main() {
        List<Zwierze> list = new LinkedList<>();
        Scanner sc = new Scanner(System.in);
        String in = sc.nextLine();
        if ("pies".equals(in))
            list.add(new Pies());
        else if ("kot".equals(in))
            list.add(new Kot());
        //powtórz wczytywanie ile razy chcesz
        for (Zwierze zwierze : list)
            System.out.println(zwierze.wydajOdglos());
    }
}
1

Może CSharpowo bardziej zadziała na wyobraźnie, bo tam każdy interfejs powinien zaczynać się od litery I:

namespace Sharp {
    interface INoiceable {
        void MakeNoice();
    }

    class Harley : INoiceable {
        public void MakeNoice() {
            Console.WriteLine("Harley: bur bur bur bur bur");
        }
    }

    class Speeder : INoiceable {
        public void MakeNoice() {
            Console.WriteLine("Spreeder: niiiiijuuuuuuuuuuuu");
        }
    }

    class Program {
        static void Main(string[] args) {

            // Lista obiektów INoicable - luźna referencja;
            var vehicles = new List<INoiceable> { new Harley(), new Speeder() };

            // Wywołanie `MakeNoice()` - masz pewność, że taka metoda posiada
            // swoją implementację i nie martwisz się, że jej wywołanie nie zadziała
            // (pomijając oczywiście błedy w jej ciele.
            foreach (var v in vehicles) v.MakeNoice();
        }
    }
}

Wyraźnie tutaj widzisz, że masz listę interfejsów w mainie - każdy obiekt, który będziesz chciał wsadzić do tej listy musi dziedziczyć od INoiseable i mieć implementację MakeNoice(). O to chodzi.

1

Na wypadek gdybyś zastanawiał się czy nie można tu po prostu użyć dziedziczenia: niby można, ale Zwierzę, Samochód czy Motor to byłyby klasy abstrakcyjne, same w sobie nie występują w przyrodzie. Jako że wszystkie ich metody również są abstrakcyjne (nie mamy dla nich żadnej wspólnej implementacji) byłoby to dokładnie to samo co interfejs, z tą różnicą że dziedziczyć można tylko po 1 klasie.

0
Shalom napisał(a):

@marcin43210 najprosciej będzie jak sam dojdziesz do tego po co są interfejsy. Napisz proszę uniwersalną funkcje min która dla podanych 2 obiektów zwraca ten który jest mniejszy.

A mógłbyś mi powiedzieć co wg Ciebie oznacza 'uniwersalną'?
public int min(int a, int b)
{
if(a>b)
return b;
else
return a;
}

Tylko, że to mi nie wygląda na uniwersalną bo działa tylko dla intów i nic więcej... Dobrzy mysle?

1

@marcin43210 tak, tym sie różni funkcja uniwersalna od tej którą napisałeś, że ja chciałbym porównywać różne rodzaje obiektów, nie tylko inty. Ale popatrz na tą swoją funkcje. Polegasz w niej tylko na tym że istnieje operator > dla obiektów a i b. A gdyby go zamienić na metodę isSmaller()? A gdyby to była metoda któregoś z tych obiektów? if(a.isSmallerThan(b)). Świta ci coś?

0
Shalom napisał(a):

@marcin43210 tak, tym sie różni funkcja uniwersalna od tej którą napisałeś, że ja chciałbym porównywać różne rodzaje obiektów, nie tylko inty. Ale popatrz na tą swoją funkcje. Polegasz w niej tylko na tym że istnieje operator > dla obiektów a i b. A gdyby go zamienić na metodę isSmaller()? A gdyby to była metoda któregoś z tych obiektów? if(a.isSmallerThan(b)). Świta ci coś?

Nic... :( Znaczy na pewno interfejs musialbym zrobić, ok... ale co dalej to nie wiem. Załóżmy, że w interfejsie będzie ta funkcja, ale problemem jest co ona ma zwracać? Przecież mogą być różne obiekty, więc jak powinna wyglądać jej deklaracja w interfejsie?

0

No to tylko siąść i płakać. Bo jak dalej to nie wiem to juz koniec świata. A może odpalisz jednak IDE i spróbujesz POMYŚLEĆ? Pokombinować? Napiszesz sobie trochę kod? Nie, lepiej od razu napisać że nie wiem jak.

Jak to co ona ma zwracać? A co wg ciebie powinna zwrócić metoda isSmallerThan()? Jak dla mnie boolean wystarczy bo metoda mówi czy obiekt X jest mniejszy od Y.

0
Shalom napisał(a):

No to tylko siąść i płakać. Bo jak dalej to nie wiem to juz koniec świata. A może odpalisz jednak IDE i spróbujesz POMYŚLEĆ? Pokombinować? Napiszesz sobie trochę kod? Nie, lepiej od razu napisać że nie wiem jak.

Jak to co ona ma zwracać? A co wg ciebie powinna zwrócić metoda isSmallerThan()? Jak dla mnie boolean wystarczy bo metoda mówi czy obiekt X jest mniejszy od Y.

Ok, faktycznie... nie pomyślałem. Zaraz wychodzę, ale jak wrócę to napisze wszystko i pokaże co udało mi się wymodzić. Wydaje mi się, że wiem już o co chodzi, ale zweryfikuje to praktyka.

0

o_O funkcja ma zwrócić boolean bo odpowiada na pytanie czy aktualny obiekt jest mniejszy od tego podanego. Oczywiście bez genericów trudno byłoby napisać całkiem sensowną implementacje bo jak porównać gruszkę z samochodem :P niemniej możesz sobie wprowadzic na przykład drugą metodę obok isSmallerThan(), nazwijmy tą drugą metodę getMagicScore() która dla każdego obiektu zwraca jakiegoś inta i porównanie opiera się między innymi o tą wartość (ale niekoniecznie każda klasa traktuje tą wartość tak samo!)

0

Podpowiem po CSharpowemu:

    interface IComparable<T> {
        int Compare(T first, T second);
    }

Niech funkcja zwraca:

  • -1 jeżeli first jest mniejszy od second;
  • 1 jeżeli odwrotnie;
  • 0 jeżeli sę równe.

Zamiast T może być double, int, Kaczka, Porsche etc... ale to już w klasie, która implementuje tej interfejs.

0
marcin43210 napisał(a):
Shalom napisał(a):

No to tylko siąść i płakać. Bo jak dalej to nie wiem to juz koniec świata. A może odpalisz jednak IDE i spróbujesz POMYŚLEĆ? Pokombinować? Napiszesz sobie trochę kod? Nie, lepiej od razu napisać że nie wiem jak.

Jak to co ona ma zwracać? A co wg ciebie powinna zwrócić metoda isSmallerThan()? Jak dla mnie boolean wystarczy bo metoda mówi czy obiekt X jest mniejszy od Y.

Ok, faktycznie... nie pomyślałem. Zaraz wychodzę, ale jak wrócę to napisze wszystko i pokaże co udało mi się wymodzić. Wydaje mi się, że wiem już o co chodzi, ale zweryfikuje to praktyka.

Do nazwy funkcji jak dla mnie boolean by pasował

3

Why interface is useful
Jak dla mnie najlepsze wytłumaczenie interfejsu.

@marcin43210 Przeczytaj i powiedz, własnymi słowami, jak to rozumiesz.

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