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