Interface — είναι ένα συμβόλαιο που ορίζει ένα σύνολο αφηρημένων μεθόδων που πρέπει να υλοποιήσει μια κλάση. Σε Java και Kotlin, οι διεπαφές είναι ο κύριος μηχανισμός αφαίρεσης και πολυμορφισμού. Σε Java 8+ οι διεπαφές μπορούν να περιέχουν default και static μεθόδους, σε Kotlin — προεπιλεγμένες υλοποιήσεις. Σύμφωνα με Google Android Developers (2025), οι διεπαφές χρησιμοποιούνται στο 90% των Android έργων για τον καθορισμό επιπέδων αρχιτεκτονικής — αποθετηρίων, UseCase και υπηρεσιών.
Κύρια Σημεία
Interface — είναι ένας τύπος αναφοράς που περιέχει αφηρημένες μεθόδους, σταθερές και προεπιλεγμένες μεθόδους. Η κλάση που υλοποιεί μια διεπαφή υποχρεούται να παρέχει υλοποίηση όλων των αφηρημένων μεθόδων της. Σε Java, η διεπαφή δεν μπορεί να έχει κατάσταση (πεδία στιγμιοτύπου), η Kotlin ακολουθεί επίσης αυτόν τον περιορισμό, αλλά υποστηρίζει ιδιότητες με προσπελάσεις.
public interface Repository {
T findById(Long id);
List findAll();
T save(T entity);
void deleteById(Long id);
default long count() {
return findAll().size();
}
}
@Entity
public class UserEntity {
private Long id;
private String email;
// getters και setters
}
public class UserRepositoryImpl implements Repository {
private final EntityManager em;
public UserEntity findById(Long id) {
return em.find(UserEntity.class, id);
}
public List findAll() {
return em.createQuery("FROM UserEntity", UserEntity.class)
.getResultList();
}
// υπόλοιπες μέθοδοι
}Repository<T> — μια γενική διεπαφή με μεθόδους CRUD. Η προεπιλεγμένη μέθοδος count() παρέχει μια προεπιλεγμένη υλοποίηση που μπορεί να παρακαμφθεί. Το UserRepositoryImpl υλοποιεί τη διεπαφή, χρησιμοποιώντας EntityManager για πρόσβαση σε δεδομένα. Αυτή η προσέγγιση επιτρέπει τη δοκιμή του επιπέδου δεδομένων μέσω mock της διεπαφής χωρίς πραγματική βάση δεδομένων.
Η επιλογή μεταξύ διεπαφής και αφηρημένης κλάσης εξαρτάται από την ύπαρξη κοινής κατάστασης και σχέσεων μεταξύ τύπων. Η διεπαφή ορίζει ένα συμβόλαιο (τι μπορεί να κάνει η κλάση), η αφηρημένη κλάση — μια κοινή υλοποίηση (τι είναι η κλάση).
| Κριτήριο | Interface | Abstract Class |
|---|---|---|
| Κατάσταση (πεδία) | Μόνο static final σταθερές | Ναι, οποιαδήποτε πεδία |
| Κατασκευαστές | Όχι | Ναι |
| Πολλαπλή κληρονομικότητα | Ναι (implements) | Όχι (extends ένα) |
| Τροποποιητές πρόσβασης | public (Java 8-), default μέθοδοι | Όλοι (private, protected, public) |
| Πότε να χρησιμοποιείται | Συμβόλαιο για μη σχετικές κλάσεις | Κοινή βάση για συγγενικές κλάσεις |
Στην Clean Architecture, οι διεπαφές τοποθετούνται στο εσωτερικό επίπεδο (domain), και οι υλοποιήσεις — στο εξωτερικό (data). Αυτό επιτρέπει την τήρηση του Κανόνα Εξάρτησης: το εξωτερικό επίπεδο εξαρτάται από το εσωτερικό, όχι το αντίστροφο. Οι αφηρημένες κλάσεις χρησιμοποιούνται συχνότερα για μεθόδους προτύπου (Template Method pattern).
Οι διεπαφές Kotlin είναι πιο ευέλικτες από της Java: μπορούν να δηλώνουν αφηρημένες ιδιότητες και να παρέχουν υλοποιήσεις μεθόδων. Σε αντίθεση με τη Java, η Kotlin υποστηρίζει εκπροσώπηση μέσω της λέξης-κλειδιού by, μειώνοντας σημαντικά τον boilerplate κώδικα κατά την υλοποίηση του προτύπου Delegate.
interface ApiService {
val baseUrl: String // αφηρημένη ιδιότητα
suspend fun fetchData(): Result>
fun getEndpoint(path: String): String {
return "$baseUrl/$path" // προεπιλεγμένη υλοποίηση
}
}
class RetrofitApiService(
override val baseUrl: String
) : ApiService {
private val client = Retrofit.Builder()
.baseUrl(baseUrl)
.build()
override suspend fun fetchData(): Result>
{
// υλοποίηση αιτήματος
}
}
// Εκπροσώπηση μέσω by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
class UserService(logger: Logger) : Logger by loggerUserService υλοποιεί τη διεπαφή Logger μέσω εκπροσώπησης (by). Όλες οι κλήσεις log() ανακατευθύνονται στο αντικείμενο logger χωρίς να γράφονται μέθοδοι wrapper. Αυτό είναι ένα παράδειγμα σύνθεσης που σε Java θα απαιτούσε 5-10 γραμμές boilerplate κώδικα.
Clean Architecture για Android χωρίζει ξεκάθαρα την εφαρμογή σε επίπεδα. Οι διεπαφές παίζουν ρόλο συνόρων μεταξύ επιπέδων: το Domain ορίζει διεπαφές αποθετηρίων και UseCase, το Data παρέχει υλοποιήσεις. Αυτό επιτρέπει την αντικατάσταση υλοποιήσεων χωρίς αλλαγή επιχειρηματικής λογικής — βασικό πλεονέκτημα κατά τη μετάβαση από Room σε Firebase ή από REST σε GraphQL.
// Επίπεδο Domain — διεπαφή (συμβόλαιο)
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun updateUser(user: User)
}
// Επίπεδο Domain — use case (εξαρτάται από διεπαφή)
class GetUserUseCase(
private val repository: UserRepository
) {
suspend operator fun invoke(id: String): Result {
return runCatching { repository.getUser(id) }
}
}
// Επίπεδο Data — υλοποίηση
class UserRepositoryImpl(
private val api: UserApi,
private val dao: UserDao
) : UserRepository {
override suspend fun getUser(id: String): User {
val cached = dao.getUser(id)
if (cached != null) return cached
val remote = api.fetchUser(id)
dao.insertUser(remote)
return remote
}
// updateUser...
}UserRepository — διεπαφή στο επίπεδο domain. Το GetUserUseCase εξαρτάται από τη διεπαφή, όχι από την υλοποίηση. Το UserRepositoryImpl στο επίπεδο data υλοποιεί τη διεπαφή, συνδυάζοντας API και τοπική βάση δεδομένων. Αυτή η αρχιτεκτονική επιτρέπει τη δοκιμή UseCase με mock αποθετήριο χωρίς ρύθμιση βάσης δεδομένων ή δικτύου.
Οι default μέθοδοι προστέθηκαν σε Java 8 για εξελικτική επέκταση διεπαφών χωρίς παραβίαση της συμβατότητας προς τα πίσω. Αν το ArrayList δεν είχε υλοποιήσει τη νέα μέθοδο stream() που προστέθηκε στη Collection, ο παλιός κώδικας θα συνέχιζε να λειτουργεί. Οι static μέθοδοι σε διεπαφές εξυπηρετούν για βοηθητικά εργαλεία που σχετίζονται με τη διεπαφή — εναλλακτική έναντι βοηθητικών κλάσεων.
public interface Vehicle {
void start();
void stop();
default void honk() {
System.out.println("Beep!");
}
static Vehicle of(String type) {
if ("car".equals(type)) return new Car();
return new Bicycle();
}
// σταθερά
String CATEGORY = "transport";
}Οι default μέθοδοι λύνουν το πρόβλημα του διαμαντιού (diamond problem): αν μια κλάση υλοποιεί δύο διεπαφές με την ίδια default μέθοδο, ο μεταγλωττιστής απαιτεί ρητή παρακάμψη. Οι static μέθοδοι καλούνται μέσω του ονόματος της διεπαφής — Vehicle.of("car"), χωρίς στιγμιότυπο.
Λάθη στον σχεδιασμό διεπαφών οδηγούν σε εύθραυστο κώδικα, δυσκολίες δοκιμής και παραβιάσεις SOLID. Ας εξετάσουμε τρία συνηθισμένα προβλήματα.
Μια διεπαφή με 15+ μεθόδους παραβιάζει την Interface Segregation Principle (ISP). Παράδειγμα — το παλιό java.util.Dictionary με 10+ μεθόδους. Λύση: διαίρεση σε πολλές μικρές διεπαφές — ReadableRepository, WritableRepository, SearchableRepository. Ο πελάτης (υπηρεσία) εξαρτάται μόνο από τις μεθόδους που χρειάζεται.
Η δημιουργία διεπαφής για κάθε κλάση χωρίς πραγματική ανάγκη για πολυμορφισμό είναι το αντί-πρότυπο Interface overkill. Σημάδι: η διεπαφή έχει ακριβώς μία υλοποίηση και δεν υπάρχουν σχέδια για προσθήκη εναλλακτικών. Λύση: προσθέστε διεπαφή μόνο όταν εμφανίζεται δεύτερη παραλλαγή υλοποίησης ή ανάγκη για mock σε δοκιμές.
Συχνές Ερωτήσεις
Interface ορίζει μόνο το συμβόλαιο (υπογραφές μεθόδων), δεν μπορεί να έχει κατάσταση και υποστηρίζει πολλαπλή κληρονομικότητα. Abstract class μπορεί να περιέχει πεδία, κατασκευαστές και υλοποιημένες μεθόδους, αλλά μια κλάση μπορεί να κληρονομήσει μόνο μία αφηρημένη κλάση. Από Java 8, οι διεπαφές απέκτησαν default και static μεθόδους, μειώνοντας το χάσμα.
Ναι, σε Java και Kotlin οι διεπαφές υποστηρίζουν κληρονομικότητα. public interface AdvancedRepository<T> extends Repository<T>, Pageable — μια διεπαφή που συνδυάζει δύο άλλες. Η κλάση που υλοποιεί AdvancedRepository πρέπει να υλοποιήσει όλες τις μεθόδους και των δύο γονικών διεπαφών. Η πολλαπλή κληρονομικότητα επιτρέπεται μόνο για διεπαφές.
Συναρτησιακή διεπαφή — μια διεπαφή με μία αφηρημένη μέθοδο (SAM — Single Abstract Method). Η σημείωση @FunctionalInterface εγγυάται αυτόν τον περιορισμό. Παραδείγματα: Runnable, Callable, Comparator, Consumer. Οι συναρτησιακές διεπαφές αποτελούν τη βάση των λάμδα εκφράσεων σε Java 8: () -> System.out.println() υλοποιεί Runnable.
Οι default μέθοδοι επιτρέπουν την προσθήκη νέων μεθόδων σε μια διεπαφή χωρίς αλλαγή όλων των υλοποιούντων κλάσεων. Για παράδειγμα, η Java 8 πρόσθεσε stream() στη Collection ως default μέθοδο. Χωρίς αυτόν τον μηχανισμό, οι foreach(), stream() και άλλες μέθοδοι θα απαιτούσαν αλλαγή χιλιάδων κλάσεων στο JDK. Default — ένας συμβατός προς τα πίσω τρόπος επέκτασης.
Kotlin απαγορεύει την πολλαπλή κληρονομικότητα κλάσεων, αλλά επιτρέπει την πολλαπλή υλοποίηση διεπαφών. Αν δύο διεπαφές έχουν μια μέθοδο με την ίδια υπογραφή και προεπιλεγμένη υλοποίηση, ο μεταγλωττιστής απαιτεί ρητή παρακάμψη με κλήση super<InterfaceName>.method(). Αυτό λύνει το πρόβλημα του διαμαντιού σε επίπεδο μεταγλώττισης.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης