Dlaczego interfejsy są ważne
Interfejsy to jeden z najważniejszych mechanizmów w Javie, który pozwala tworzyć elastyczny i łatwy w utrzymaniu kod. W projektach enterprise są niezbędne – pozwalają różnym zespołom pracować równolegle nad różnymi częściami systemu, nie blokując się nawzajem.
Co się nauczysz:
- Czym dokładnie jest interfejs i jak różni się od klasy
- Jak tworzyć i implementować interfejsy w praktyce
- Dlaczego interfejsy są kluczowe dla polimorfizmu
- Kiedy używać interfejsów zamiast dziedziczenia
- Najlepsze praktyki projektowania interfejsów
Czym jest interfejs w Javie
Oto podstawowy przykład interfejsu:
// Definicja interfejsu public interface Drawable { void draw(); // metoda abstrakcyjna void setColor(String color); // wszystkie metody są public abstract String getDescription(); } // Implementacja interfejsu public class Circle implements Drawable { private String color = "black"; private int radius; public Circle(int radius) { this.radius = radius; } @Override public void draw() { System.out.println("Rysuje koło o promieniu " + radius + " kolorem " + color); } @Override public void setColor(String color) { this.color = color; } @Override public String getDescription() { return "Koło o promieniu " + radius; } }
Interfejs vs Klasa – kluczowe różnice
Interfejs | Klasa |
---|---|
Tylko metody abstrakcyjne* | Może mieć metody konkretne |
Nie ma konstruktorów | Ma konstruktory |
Wszystkie pola są public static final | Pola mogą być różnych typów |
Klasa może implementować wiele interfejsów | Klasa może dziedziczyć tylko z jednej klasy |
Używany do definiowania „umowy” | Używana do tworzenia obiektów |
Polimorfizm z interfejsami
Największą zaletą interfejsów jest możliwość polimorfizmu – traktowania różnych obiektów w ten sam sposób:
public class Rectangle implements Drawable { private String color = "black"; private int width, height; public Rectangle(int width, int height) { this.width = width; this.height = height; } @Override public void draw() { System.out.println("Rysuje prostokąt " + width + "x" + height + " kolorem " + color); } @Override public void setColor(String color) { this.color = color; } @Override public String getDescription() { return "Prostokąt " + width + "x" + height; } } // Polimorfizm w akcji public class DrawingApplication { public static void main(String[] args) { // Różne obiekty, ale ten sam interfejs Drawable[] shapes = { new Circle(5), new Rectangle(10, 20), new Circle(3) }; // Traktujemy wszystkie jednakowo for (Drawable shape : shapes) { shape.setColor("red"); shape.draw(); System.out.println(shape.getDescription()); } } }
Kiedy używać interfejsów
**Używaj interfejsów gdy:**
– Chcesz zdefiniować kontrakt dla grupy klas
– Potrzebujesz „multiple inheritance” funkcjonalności
– Tworzysz API dla innych programistów
– Chcesz łatwo testować kod (mocking)
– Projektujesz plug-in system
**Przykład z życia:** System płatności
public interface PaymentProcessor { boolean processPayment(double amount); String getPaymentMethod(); boolean isAvailable(); } public class PayPalProcessor implements PaymentProcessor { @Override public boolean processPayment(double amount) { // Logika PayPal System.out.println("Przetwarzam " + amount + " PLN przez PayPal"); return true; } @Override public String getPaymentMethod() { return "PayPal"; } @Override public boolean isAvailable() { return true; // sprawdź połączenie z PayPal API } } public class CreditCardProcessor implements PaymentProcessor { @Override public boolean processPayment(double amount) { // Logika karty kredytowej System.out.println("Przetwarzam " + amount + " PLN kartą kredytową"); return true; } @Override public String getPaymentMethod() { return "Credit Card"; } @Override public boolean isAvailable() { return true; // sprawdź połączenie z bankiem } }
Najlepsze praktyki
**1. Nazewnictwo interfejsów**
– Używaj rzeczowników opisujących możliwości: `Readable`, `Drawable`, `Serializable`
– Lub rzeczowników opisujących typ: `PaymentProcessor`, `DatabaseConnection`
**2. Rozmiar interfejsu**
– Trzymaj interfejsy małe i spójne
– Lepiej mieć kilka małych interfejsów niż jeden ogromny
**3. Dokumentacja**
– Opisz co interfejs reprezentuje
– Dokumentuj oczekiwane zachowanie metod
Interfejsy w Spring Framework
W Spring Framework interfejsy są wszędzie. Przykład z Spring Data JPA (dostępne od wersji 1.0):
// Spring automatycznie tworzy implementację public interface UserRepository extends JpaRepository{ List findByEmail(String email); List findByAgeGreaterThan(int age); } // Użycie w serwisie @Service public class UserService { @Autowired private UserRepository userRepository; public User createUser(String email, int age) { User user = new User(email, age); return userRepository.save(user); // metoda z interfejsu JpaRepository } }
Tak! Interfejs może rozszerzać (extends) jeden lub więcej innych interfejsów. Klasa implementująca taki interfejs musi zaimplementować wszystkie metody z całej hierarchii.
Interfejs może mieć tylko stałe – wszystkie pola są automatycznie public static final. Używa się ich rzadko, głównie do przechowywania stałych związanych z interfejsem.
Interfejs gdy definiujesz „umowę” – co klasa ma umieć. Klasa abstrakcyjna gdy masz wspólny kod do współdzielenia między klasami potomnymi. Interfejs to „co”, klasa abstrakcyjna to „jak”.
Nie możesz użyć new na interfejsie. Możesz tylko utworzyć referencję typu interfejs wskazującą na obiekt klasy implementującej ten interfejs.
Klasa może implementować dowolną liczbę interfejsów. To główna zaleta interfejsów – Java ma tylko pojedyncze dziedziczenie klas, ale wielokrotne „dziedziczenie” interfejsów.
🚀 Zadanie dla Ciebie
Stwórz interfejs Vehicle z metodami start(), stop() i getMaxSpeed(). Następnie zaimplementuj go w klasach Car i Bicycle. Napisz metodę, która przyjmuje tablicę Vehicle[] i wywołuje na każdym start().
Przydatne zasoby:
Czy używasz już interfejsów w swoich projektach? Podziel się w komentarzach jakie wyzwania napotkałeś podczas implementacji!