Interface: ουσία, συμβόλαια σε Java και Kotlin για Android

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-02-18 Χρόνος ανάγνωσης: 9 λεπ

Interface — είναι ένα συμβόλαιο που ορίζει ένα σύνολο αφηρημένων μεθόδων που πρέπει να υλοποιήσει μια κλάση. Σε Java και Kotlin, οι διεπαφές είναι ο κύριος μηχανισμός αφαίρεσης και πολυμορφισμού. Σε Java 8+ οι διεπαφές μπορούν να περιέχουν default και static μεθόδους, σε Kotlin — προεπιλεγμένες υλοποιήσεις. Σύμφωνα με Google Android Developers (2025), οι διεπαφές χρησιμοποιούνται στο 90% των Android έργων για τον καθορισμό επιπέδων αρχιτεκτονικής — αποθετηρίων, UseCase και υπηρεσιών.

Κύρια Σημεία

  • Interface — αφηρημένος τύπος που ορίζει υπογραφές μεθόδων χωρίς υλοποίηση (πριν Java 8)
  • Implements — λέξη-κλειδί που συνδέει μια κλάση με μια διεπαφή; μια κλάση μπορεί να υλοποιήσει πολλαπλές διεπαφές
  • Default method — μέθοδος με υλοποίηση σε διεπαφή Java, προστέθηκε σε Java 8 για συμβατότητα προς τα πίσω
  • Kotlin interface υποστηρίζει ιδιότητες με getters και υλοποιήσεις μεθόδων, αντικαθιστώντας abstract class σε πολλά σενάρια
  • Markup interface — κενή διεπαφή που χρησιμοποιείται ως ετικέτα (Serializable, Cloneable, RandomAccess)

Τι είναι το Interface σε Java και Kotlin;

Interface — είναι ένας τύπος αναφοράς που περιέχει αφηρημένες μεθόδους, σταθερές και προεπιλεγμένες μεθόδους. Η κλάση που υλοποιεί μια διεπαφή υποχρεούται να παρέχει υλοποίηση όλων των αφηρημένων μεθόδων της. Σε Java, η διεπαφή δεν μπορεί να έχει κατάσταση (πεδία στιγμιοτύπου), η Kotlin ακολουθεί επίσης αυτόν τον περιορισμό, αλλά υποστηρίζει ιδιότητες με προσπελάσεις.

Java
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 vs abstract class: πότε να διαλέξεις τι

Η επιλογή μεταξύ διεπαφής και αφηρημένης κλάσης εξαρτάται από την ύπαρξη κοινής κατάστασης και σχέσεων μεταξύ τύπων. Η διεπαφή ορίζει ένα συμβόλαιο (τι μπορεί να κάνει η κλάση), η αφηρημένη κλάση — μια κοινή υλοποίηση (τι είναι η κλάση).

ΚριτήριοInterfaceAbstract Class
Κατάσταση (πεδία)Μόνο static final σταθερέςΝαι, οποιαδήποτε πεδία
ΚατασκευαστέςΌχιΝαι
Πολλαπλή κληρονομικότηταΝαι (implements)Όχι (extends ένα)
Τροποποιητές πρόσβασηςpublic (Java 8-), default μέθοδοιΌλοι (private, protected, public)
Πότε να χρησιμοποιείταιΣυμβόλαιο για μη σχετικές κλάσειςΚοινή βάση για συγγενικές κλάσεις

Στην Clean Architecture, οι διεπαφές τοποθετούνται στο εσωτερικό επίπεδο (domain), και οι υλοποιήσεις — στο εξωτερικό (data). Αυτό επιτρέπει την τήρηση του Κανόνα Εξάρτησης: το εξωτερικό επίπεδο εξαρτάται από το εσωτερικό, όχι το αντίστροφο. Οι αφηρημένες κλάσεις χρησιμοποιούνται συχνότερα για μεθόδους προτύπου (Template Method pattern).

Interface σε Kotlin: ιδιότητες και εκπρόσωποι

Οι διεπαφές Kotlin είναι πιο ευέλικτες από της Java: μπορούν να δηλώνουν αφηρημένες ιδιότητες και να παρέχουν υλοποιήσεις μεθόδων. Σε αντίθεση με τη Java, η Kotlin υποστηρίζει εκπροσώπηση μέσω της λέξης-κλειδιού by, μειώνοντας σημαντικά τον boilerplate κώδικα κατά την υλοποίηση του προτύπου Delegate.

Kotlin
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 logger

UserService υλοποιεί τη διεπαφή Logger μέσω εκπροσώπησης (by). Όλες οι κλήσεις log() ανακατευθύνονται στο αντικείμενο logger χωρίς να γράφονται μέθοδοι wrapper. Αυτό είναι ένα παράδειγμα σύνθεσης που σε Java θα απαιτούσε 5-10 γραμμές boilerplate κώδικα.

Interface στην Clean Architecture Android

Clean Architecture για Android χωρίζει ξεκάθαρα την εφαρμογή σε επίπεδα. Οι διεπαφές παίζουν ρόλο συνόρων μεταξύ επιπέδων: το Domain ορίζει διεπαφές αποθετηρίων και UseCase, το Data παρέχει υλοποιήσεις. Αυτό επιτρέπει την αντικατάσταση υλοποιήσεων χωρίς αλλαγή επιχειρηματικής λογικής — βασικό πλεονέκτημα κατά τη μετάβαση από Room σε Firebase ή από REST σε GraphQL.

Kotlin
// Επίπεδο 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 και static μέθοδοι σε Java 8+

Οι default μέθοδοι προστέθηκαν σε Java 8 για εξελικτική επέκταση διεπαφών χωρίς παραβίαση της συμβατότητας προς τα πίσω. Αν το ArrayList δεν είχε υλοποιήσει τη νέα μέθοδο stream() που προστέθηκε στη Collection, ο παλιός κώδικας θα συνέχιζε να λειτουργεί. Οι static μέθοδοι σε διεπαφές εξυπηρετούν για βοηθητικά εργαλεία που σχετίζονται με τη διεπαφή — εναλλακτική έναντι βοηθητικών κλάσεων.

Java
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. Ας εξετάσουμε τρία συνηθισμένα προβλήματα.

Interface Pollution — πάρα πολλές μέθοδοι σε μία διεπαφή

Μια διεπαφή με 15+ μεθόδους παραβιάζει την Interface Segregation Principle (ISP). Παράδειγμα — το παλιό java.util.Dictionary με 10+ μεθόδους. Λύση: διαίρεση σε πολλές μικρές διεπαφές — ReadableRepository, WritableRepository, SearchableRepository. Ο πελάτης (υπηρεσία) εξαρτάται μόνο από τις μεθόδους που χρειάζεται.

Υπερβολική αφαίρεση — διεπαφή για κάθε κλάση

Η δημιουργία διεπαφής για κάθε κλάση χωρίς πραγματική ανάγκη για πολυμορφισμό είναι το αντί-πρότυπο Interface overkill. Σημάδι: η διεπαφή έχει ακριβώς μία υλοποίηση και δεν υπάρχουν σχέδια για προσθήκη εναλλακτικών. Λύση: προσθέστε διεπαφή μόνο όταν εμφανίζεται δεύτερη παραλλαγή υλοποίησης ή ανάγκη για mock σε δοκιμές.

Συχνές Ερωτήσεις

Σε τι διαφέρει η interface από abstract class σε Java;

Interface ορίζει μόνο το συμβόλαιο (υπογραφές μεθόδων), δεν μπορεί να έχει κατάσταση και υποστηρίζει πολλαπλή κληρονομικότητα. Abstract class μπορεί να περιέχει πεδία, κατασκευαστές και υλοποιημένες μεθόδους, αλλά μια κλάση μπορεί να κληρονομήσει μόνο μία αφηρημένη κλάση. Από Java 8, οι διεπαφές απέκτησαν default και static μεθόδους, μειώνοντας το χάσμα.

Μπορεί μια διεπαφή να κληρονομήσει άλλη διεπαφή;

Ναι, σε Java και Kotlin οι διεπαφές υποστηρίζουν κληρονομικότητα. public interface AdvancedRepository<T> extends Repository<T>, Pageable — μια διεπαφή που συνδυάζει δύο άλλες. Η κλάση που υλοποιεί AdvancedRepository πρέπει να υλοποιήσει όλες τις μεθόδους και των δύο γονικών διεπαφών. Η πολλαπλή κληρονομικότητα επιτρέπεται μόνο για διεπαφές.

Τι είναι η συναρτησιακή διεπαφή σε Java;

Συναρτησιακή διεπαφή — μια διεπαφή με μία αφηρημένη μέθοδο (SAM — Single Abstract Method). Η σημείωση @FunctionalInterface εγγυάται αυτόν τον περιορισμό. Παραδείγματα: Runnable, Callable, Comparator, Consumer. Οι συναρτησιακές διεπαφές αποτελούν τη βάση των λάμδα εκφράσεων σε Java 8: () -> System.out.println() υλοποιεί Runnable.

Γιατί χρειάζονται οι default μέθοδοι σε Java διεπαφές;

Οι default μέθοδοι επιτρέπουν την προσθήκη νέων μεθόδων σε μια διεπαφή χωρίς αλλαγή όλων των υλοποιούντων κλάσεων. Για παράδειγμα, η Java 8 πρόσθεσε stream() στη Collection ως default μέθοδο. Χωρίς αυτόν τον μηχανισμό, οι foreach(), stream() και άλλες μέθοδοι θα απαιτούσαν αλλαγή χιλιάδων κλάσεων στο JDK. Default — ένας συμβατός προς τα πίσω τρόπος επέκτασης.

Πώς λύνει η Kotlin το πρόβλημα της πολλαπλής κληρονομικότητας;

Kotlin απαγορεύει την πολλαπλή κληρονομικότητα κλάσεων, αλλά επιτρέπει την πολλαπλή υλοποίηση διεπαφών. Αν δύο διεπαφές έχουν μια μέθοδο με την ίδια υπογραφή και προεπιλεγμένη υλοποίηση, ο μεταγλωττιστής απαιτεί ρητή παρακάμψη με κλήση super<InterfaceName>.method(). Αυτό λύνει το πρόβλημα του διαμαντιού σε επίπεδο μεταγλώττισης.

Σύνοψη

  • Interface — συμβόλαιο που ορίζει μεθόδους προς υλοποίηση; κύριος μηχανισμός πολυμορφισμού σε Java και Kotlin
  • Java 8+ πρόσθεσε default και static μεθόδους σε διεπαφές, μειώνοντας το χάσμα με τις αφηρημένες κλάσεις
  • Kotlin interfaces υποστηρίζουν αφηρημένες ιδιότητες, προεπιλεγμένες υλοποιήσεις και εκπροσώπηση μέσω by
  • Clean Architecture χρησιμοποιεί διεπαφές ως σύνορα μεταξύ επιπέδων Domain και Data
  • Interface Segregation Principle απαιτεί διαχωρισμό μεγάλων διεπαφών σε εξειδικευμένες
  • Συναρτησιακές διεπαφές (Single Abstract Method) — βάση λάμδα εκφράσεων και Stream API
  • Σύσταση: προσθέστε διεπαφή όταν εμφανίζεται δεύτερη υλοποίηση ή ανάγκη για mock σε δοκιμές

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης