Z abstrakcją masz do czynienia na co dzień. Weźmy taką skrzynie biegów w aucie. Skrzynia biegów jest tutaj interfejsem, to co dzieję się pod spodem gdy zmieniasz bieg Cię nie interesuje. Są to szczegóły implementacyjne. Interfejs to ruszanie lewarka. Specjalistą nie jestem, ale załóżmy, że w bebechach skrzyni biegów mogą być wykorzystane różne technologie. Ciebie to jednak nie interesuje, bo Ty musisz jedynie znać interfejs skrzyni biegów, żeby z niej korzystać. Jak wsiądziesz do innego auta, w którym mechanizm zmiany biegów jest rozwiązany jakoś inaczej to Ciebie to nie interesuje, bo Tobie wystarczy jednie wiedza o tym jak z tej skrzyni korzystać. Implementacja jest schowana.
Inny przykład - pilot do telewizora. Obchodzi Cię, w jaki sposób on przesyła wiadomości do telewizora? Nie. Bo jest to szczegół implementacyjny. Różne piloty przesyłają wiadomości w różny sposób, ale mają ten samy interfejs:
interface Pilot {
void wlaczTV();
}
class PilotBluetooth implements Pilot{
@Override
public void wlaczTV() {
//logika specyficzna dla wlaczenia telewizora po blutucie
}
}
class PilotPodczerwien implements Pilot{
@Override
public void wlaczTV() {
//logika specyficzna dla wlaczenia telewizora po podczerwieni
}
}
Dzięki takiej abstrakcji możesz poruszać się w skomplikowanym systemie bez poznawania każdej klasy. Możesz operować na abstrakcjach (interfejsach). Możesz korzystać z różnych pilotów nie wiedzać wcale jaka magia dzieje się pod spodem, żeby pilot włączyl telewizor.
To był przykład na zrozumienie. Teraz przykład bardziej programistyczny:
class Customer {}
interface CustomerStorage{
void add(Customer c) throws IOException;
void remove(Customer c) throws IOException;
}
class CustomerFileStorage implements CustomerStorage{
//file handling logic
}
class CustomerSQLDatabaseStorage implements CustomerStorage{
//sql database logic
}
W ten sposób za pomocą abstrakcji uzyskujemy identyczną sytuacje jak ze skrzynią biegów czy pilotem. Programiste, który korzysta z klas implementujących CustomerStorage nie obchodzą bebechy klasy tylko jej interfejs. Liczy się, że dana implementacja będzie umożliwiać zapisywanie i usuwanie Customerów z "przechowalni danych". Jak to zrobi? Nieistotne. Szczegóły implementacyjne zostały "ukryte".
Jak w kodzie używa się ukrytych implementacji? Zmienna typu interfejsu może przechowywać różne implementacje tego interfejsu:
CustomerStorage storage = new CustomerSQLDatabaseStorage();
storage = new CustomerFileStorage();
Więc można napisać klasę korzystającą z jakiegoś rodzaju przechowalni customerów, która nawet nie wie z której klasy będzie korzystać:
class Library{
CustomerStorage storage;
Library(CustomerStorage storage){
this.storage = storage;
}
void registerCustomer(Customer c){
storage.add(c);
}
}