Różnica pomiędzy interfejsem a wywołaniem metody

0

Czy może mi ktoś prostym językiem wytłumaczyć różnicę pomiędzy implementacją interfejsu do danej klasy a przywołaniem metody która zwraca tekst?

Na poniższym przykładzie:
Tworzę Interfejs o nazwie Czyszczenie która zawiera:

void cleaning();

Tworzę klasę Patelnia którą implementuję interfejsem:

@Override
public void cleaning() {
   System.out.println("Po smazeniu automatyczne oczyszczanie");
}

W klasie Main tworzę obiekt klasy Patelnia o nazwie patelnia i wywołuję patelnia.cleaning(); i wyświetla się napis z Interfejsu

OK, to rozumiem. ALE

Co jeżeli np. nie tworzę Interfejsu i w klasie Patelnia tworzę po prostu sobie metodę:

void cleaningMethod() {
   System.out.println("Po smazeniu automatyczne oczyszczanie");
}

Następnie w klasie Main robię to samo, czyli tworzę obiekt klasy Patelnia o nazwie patelnia i wywołuję metodę:

patelnia.cleaningMethod();

i również wyświetla mi się napis tyle że z metody.

Jaka jest różnica pomiędzy tymi dwoma instrukcjami? Po co są interfejsy mając na uwagę powyższy przykład?

5

Bo rozpatrujesz tylko patelnie, zapominasz np o drukarce, też ma autocleaning!
więc jak nie będzie interfejsu to alternatywa:

void cleanall(IAutocleaning[] tb) { for(IAutocleaning item:tb) item.cleaning(); }

Będzie:

void cleanall(Object[] tb)
{
  for(Object item:tb)
  {
    if(item instanceof Patelnia) ((Patelnia)item).cleaningMethod();
    else if(item instanceof Printer) ((Printer)item).callAutoClean();
    else if(item instanceof ...) ...
    else if(item instanceof ...) ...
    else if(item instanceof ...) ...
  }
}
0

Zrozumiałem, dzięki!

3
Java91 napisał(a):

Jaka jest różnica pomiędzy tymi dwoma instrukcjami?

Pomiędzy przykładami które dodałeś, nie ma żadnej. I tak wołasz metodę na obiekcie. W przykładzie który zaprezentowałeś, dodałeś interfejs, owszem, ale nie korzystasz z niego. Nadal wołasz cleaning() na Patelnia.

Zadałeś za to inne pytanie:

Java91 napisał(a):

Po co są interfejsy mając na uwagę powyższy przykład?

Mając na uwadze powyższy przykład - po nic.

Ale mają inne zastosowanie, mianowicie polimorfizm.

W językach dynamicznie typowanych, np Python Ruby, PHP, JavaScript, możesz sobie stworzyć dwie klasy, File oraz Buffer, z metodami write(content)

class File {
  function write($content) {
    // zapisz tutaj treść do pliku
  }
}
class Buffer {
  function write($content) {
    // zapisz tutaj treść do bufora
  }
}

i teraz w dynamicznie typowanym języku, możesz po prostu wywołać metodę write() na jednym z tych obiektów.

call(new File());
call(new Buffer());

function call($object) {
  $object->write(); // zarówno kod z File jak i Buffer "po prostu zadziała", 
                    // mają taką samą nazwę i sygnaturę
}

To jest polimofrizm. Pozwala osiągać Dependency Inversion, Open/Close i robić parę innych super rzeczy. To jest w języku dynamicznie typowanym. Oczywiście jeśli przekazałbyś obiekt, który akurat nie ma takie metody, albo ta metoda ma niekompatybilną sygnaturę dostałbyś błąd w trakcie działania programu.

W języku silnie typowanym, jak Java, Kotlin, C#, etc. Taki kod nie przeszedłby "tak po prostu", gdybyś w Javie napisał coś takiego:

call(new File());
call(new Buffer());

void call(Object $object) {
  $object->write();  // nie ma funkcji "write" w klasie "Object"
}

to typ Object nie ma funkcji write(), więc taki kod jest niepoprawny - dostaniesz błąd w trakcie kompilacji. Tym się różni type-checking w compile-time vs. runtime.

mógłbyś to obejść na dwa sposoby, albo stworzyć dwie metody call(File file)/call(Buffer buffer), ale to jest słabe; albo nadać metodzie taki typ, który ma funkcję write. Mógłbyś to zrobić na kilka sposobów:

  1. Albo dziedziczyć File z Buffer, (ale to jest bardzo słabe)

    class Buffer { void write(String content); }
    class File extends Buffer {}
    
    call(new File());
    call(new Buffer());
    
    void call(Buffer $object) {
      $object->write();  // zadziała dla buffer oraz file
    }
    
  2. Albo dziedziczyć z wspólnego rodzica

    class Writeable {
      void write() {}
    }
    
    class File extends Writeable {
       @Override
       public void write() {
          System.out.println("Po smazeniu automatyczne oczyszczanie");
       }
    }
    class Buffer extends Writeable {
      @Override
      public void write() {
         System.out.println("Po smazeniu automatyczne oczyszczanie");
      }
    }
    
    call(new File());
    call(new Buffer());
    
    void call(Writeable $object) {
      $object->write();  // zadziała dla buffer oraz file
    }
    

    To by zadziałało, i byłoby polimorficzne, tylko niektórym się nie podoba że technicznie możnaby też zrobić wtedy call(new Writeable()). Ktoś chciałby móc użyć new File() oraz new Buffer(), ale nie new Writetable(). Komuś się może też nie podobać, że dostarczamy implementacje write() w klasie Writeable, skoro i tak ją nadpiszemy zaraz. Obie te niewygody załatwia metoda abstrakcyjna.

    abstract class Writeable {
      abstract void write(); // nie ma niepotrzebnej implementacji
    }
    
    class File extends Writeable {
       @Override
       public void write() {
          System.out.println("Po smazeniu automatyczne oczyszczanie");
       }
    }
    class Buffer extends Writeable {
      @Override
      public void write() {
         System.out.println("Po smazeniu automatyczne oczyszczanie");
      }
    }
    
    call(new File());
    call(new Buffer());
    call(new Writeable()); // nie można stworzyć instancji rodzica
    
    void call(Writeable $object) {
      $object->write();  // zadziała dla buffer oraz file
    }
    

No, i to byłby koniec dobrze zrobionego polimorfizmu, ale!

W Javie nie ma wielokrotnego dziedziczenia, więc nie możemy skorzystać z tego rozwiązania wyżej dla wielu polimorifcznych gałęzi. W innych językach, nie byłoby problemu, po prostu wystarczyłoby dziedziczyć z kilku klas które mają metody abstrakcyjne, obiekt by je implementował, i byłoby git.

Ale niestety w Javie to tak nie działa. Większość tłumaczeń podaje jako powód "problem diamentu" (co jest śmieszne, bo problem diamentu został rozwiązany dawno temu w innych językach). Dlatego wymyślono workaround w postaci interfejsu. Interfejs to nic innego jak klasa abstrakcyjna, która nie może mieć pól i wszystkie jej metody są abstrakcyjne (w Javie 8 doszły jeszcze "smaczki" w postaci "domyślnych metod", co już całkowicie zatarło różnicę między tymi bytami).

Czyli czym jest interfejs w Javie - konstruktem/typem specyfikującym zestaw metod abstrakcyjnych, pod który różni implementujący mogą dostarczyć różne implementacje, by móc być później polimorficznie wywołane.

Największą zaletą polimorfizmu, w mojej opinii, jest uniezależnienie kodu który woła metody, od kodu który dostarcza implementacje, czyli wyżej wspomniana Dependency Inversion.

1
Java91 napisał(a):

implementacją interfejsu do danej klasy a przywołaniem metody która zwraca tekst?

Tworzę klasę Patelnia którą implementuję interfejsem:

Słowa SĄ WAŻNE
Trudno osiągnąć coś abstrakcyjnego, np zrozumienie subtelności języka, bez używania poprawnego słownictwa. Wcześniej czy później zaprowadzi na manowce.

Pierwsze zdanie ma brzmieć:
implementacją danego interfejsu (X) w danej klasie (Y)

Drugie
klasa Patelnia, która implementuje interfejs

Moim zdaniem złe słownictwo wyrabia w umyśle złe myślenie.

0

To może pokolei:

  1. Klasa używająca metody main, wykorzystuje tzw. programowiane strukturalne a nie obiektowe (czyli po prostu czyta linie jedna po drugiej). Możesz to rozpoznać po słowie kluczowym static w sygnaturze metody. Takie metody są metodami tzw. klasowymi a obiektowymi.
  2. Wszelkie metody bez static są metodami obiektowymi, czyli najpierw należy stworzyć obiekt (np. poprzez new), aby później z niego skorzystać.
  3. Obiekty mają swój cykl życia, gdy metody statyczne można użyć zawsze.
  4. Interfejsy odnoszą się do sztuczki w programowaniu obiektowym o nazwie polimorfizm. Ponieważ obiekty powstają w trakcie działania aplikacji (tzw. runtime), można 'oszukać' Javę i wystawić jedynie interfejs, czyli zapewnienie, że po wywołaniu danej sygnatury, gdzieś jest zaimplementowane ciało metody.
  5. Ogólnie interfejsy pozwalają łatwo podzielić kod na mniej kawałki z racji, że wszystkie elementy nie są ze sobą ściśle połączone, tak jak w programowaniu strukturalnym. (Loose coupling)
0
developeronthego napisał(a):

To może pokolei:

  1. Klasa używająca metody main, wykorzystuje tzw. programowiane strukturalne a nie obiektowe (czyli po prostu czyta linie jedna po drugiej). Możesz to rozpoznać po słowie kluczowym static w sygnaturze metody. Takie metody są metodami tzw. klasowymi a obiektowymi.

Nie, metoda main i metody statyczne nie maja nic wspólnego z programowaniem strukturalnym (z byciem metodą też właściwie nie mają wiele wspólnego), tak samo jak sam fakt, że metoda nie została oznaczona "static" nie znaczy jeszcze, że to programowanie obiektowe (ale proszę, nie zaczynajmy tej dyskusji ponownie).

  1. Wszelkie metody bez static są metodami obiektowymi, czyli najpierw należy stworzyć obiekt (np. poprzez new), aby później z niego skorzystać.

Albo coś innego musi stworzyć taki obiekt, ale fakt, w Javie gdzieś musi zostać wstawione new (lub wywołana inna metoda tworzenia obiektu), żeby powstał.

  1. Obiekty mają swój cykl życia, gdy metody statyczne można użyć zawsze.

Ryzykowne stwierdzenie - bo jedyny ogólny cykl życia, to "obiekt stworzony" "obiekt uprzątnięty".

  1. Interfejsy odnoszą się do sztuczki w programowaniu obiektowym o nazwie polimorfizm. Ponieważ obiekty powstają w trakcie działania aplikacji (tzw. runtime), można 'oszukać' Javę i wystawić jedynie interfejs, czyli zapewnienie, że po wywołaniu danej sygnatury, gdzieś jest zaimplementowane ciało metody.

Wszystko w programowaniu obiektowym ma coś tam wspólnego z polimorfizmem. Interface to zbiór rzeczy które można wykonać na obiekcie go implementującym. Jeżeli mam np, takie try-with-resources, to wszystko czym jest zainteresowana ta instrukcja składniowa, to to, że wskazany obiekt da się "zamknąć" bo wywołaniu close(), nie jest istotne po czym dziedziczy, czy jest to uchwyt do pliku, wysyłanie muzyki do głośnika, czy kran.

  1. Ogólnie interfejsy pozwalają łatwo podzielić kod na mniej kawałki z racji, że wszystkie elementy nie są ze sobą ściśle połączone, tak jak w programowaniu strukturalnym. (Loose coupling)

Bardzo łatwo można stworzyć jeden wielki interface, który niczego nie dzieli. Ścisłe powiązanie kawałków kodu nie jest cechą programowania strukturalnego, czy obiektowego, ale programisty. To na co pozwalają dobrze zaprojektowane interface'y to na oznaczenie kompletnie nie związanych klas (nie koniecznie małych), jako spełniających jakiś tam kontrakt, w sensie posiadania określonych metod.

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