SOLID: αρχές, 5 κανόνες OOP και εφαρμογή στην ανάπτυξη

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

SOLID — πέντε αρχές αντικειμενοστραφούς προγραμματισμού, οι οποίες διατυπώθηκαν από τον Robert C. Martin (Uncle Bob) στις αρχές της δεκαετίας του 2000. Σύμφωνα με το DigitalOcean, 2024, το SOLID αποκρυπτογραφείται ως Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation και Dependency Inversion. Αυτές οι αρχές αποτελούν τη βάση της Clean Architecture και εφαρμόζονται στην ανάπτυξη Android (MVP, MVVM, Clean Architecture) και iOS (VIPER, TCA).

Κύρια σημεία

  • SOLID — ακρωνύμιο πέντε αρχών OOP: SRP, OCP, LSP, ISP, DIP, που διατυπώθηκαν από τον Robert C. Martin για τη δημιουργία ευέλικτου και συντηρήσιμου κώδικα.
  • SRP (Single Responsibility) — κάθε κλάση έχει έναν λόγο για αλλαγή, μία ευθύνη ανά module.
  • OCP (Open-Closed) — οι κλάσεις είναι ανοικτές για επέκταση αλλά κλειστές για τροποποίηση, υλοποιείται μέσω κληρονομικότητας και πολυμορφισμού.
  • LSP (Liskov Substitution) — τα αντικείμενα υποκλάσεων πρέπει να αντικαθιστούν τα αντικείμενα της βασικής κλάσης χωρίς να αλλάζει η ορθότητα του προγράμματος.
  • ISP (Interface Segregation) — οι πελάτες δεν πρέπει να εξαρτώνται από interfaces που δεν χρησιμοποιούν, τα interfaces πρέπει να είναι στενά και ειδικά.
  • DIP (Dependency Inversion) — τα modules υψηλού επιπέδου δεν εξαρτώνται από modules χαμηλού επιπέδου, και τα δύο εξαρτώνται από abstractions.

Τι είναι το SOLID; Επισκόπηση πέντε αρχών

SOLID — ένα μνημονικό ακρωνύμιο που υποδηλώνει πέντε αρχές αντικειμενοστραφούς σχεδιασμού. Ο όρος εισήχθη από τον Robert C. Martin στο άρθρο “Design Principles and Design Patterns” (2000) και αργότερα διαδόθηκε στο βιβλίο “Agile Software Development: Principles, Patterns, and Practices” (2002). Το SOLID δεν είναι framework ή βιβλιοθήκη — είναι μια συλλογή πρακτικών που κάνουν τον κώδικα λιγότερο συνδεδεμένο, πιο δοκιμάσιμο και ευκολότερο στις τροποποιήσεις.

Σύμφωνα με το Clean Coder Blog, 2014, κάθε αρχή SOLID λύνει ένα συγκεκριμένο πρόβλημα σχεδιασμού: το SRP καταπολεμά τις God-classes, το OCP — τις αλυσιδωτές αλλαγές, το LSP — την λανθασμένη κληρονομικότητα, το ISP — τα παχιά interfaces, το DIP — τη σφιχτή σύζευξη. Μαζί αποτελούν το θεμέλιο της Clean Architecture, η οποία χρησιμοποιείται σε Android projects με MVP, MVVM και MVI.

SRP: Single Responsibility Principle

Single Responsibility Principle (SRP) — η αρχή της μίας ευθύνης. Διατύπωση: “Μια κλάση πρέπει να έχει μόνο έναν λόγο για να αλλάξει.” Αυτό σημαίνει ότι κάθε module ή κλάση είναι υπεύθυνη για ακριβώς μία λειτουργικότητα ή μία οντότητα τομέα. Αν μια κλάση διαχειρίζεται τόσο τους χρήστες όσο και την αποστολή email — έχει δύο λόγους για αλλαγή, πράγμα που παραβιάζει το SRP.

Σύμφωνα με Robert C. Martin, 2002, το SRP είναι η πιο σημαντική και ταυτόχρονα η πιο συχνά παραβιαζόμενη αρχή. Στην κινητή ανάπτυξη, το SRP παραβιάζεται συχνά σε Activity/Fragment, συνδυάζοντας UI λογική, πλοήγηση, εργασία δικτύου και επιχειρηματική λογική. Λύση — διαχωρισμός κάθε επιπέδου σε ξεχωριστή κλάση: ViewModel για UI λογική, Repository για δεδομένα, NavController για πλοήγηση.

Παράδειγμα SRP: διαίρεση UserManager

Εξετάστε την κλάση UserManager, η οποία φορτώνει το προφίλ, αποθηκεύει ρυθμίσεις και στέλνει emails. Αυτές είναι τρεις διαφορετικές ευθύνες, κάθε μία πρέπει να διαχωριστεί σε ξεχωριστή κλάση: UserProfileRepository (φόρτωση), UserSettingsStorage (αποθήκευση) και EmailService (αποστολή). Ο κώδικας πελάτη (ViewModel) χρησιμοποιεί και τις τρεις μέσω Dependency Injection, και κάθε κλάση δοκιμάζεται εύκολα απομονωμένα και αλλάζει χωρίς να επηρεάζει τις άλλες.

kotlin
// ❌ Παράβαση SRP: Το Activity γνωρίζει για δίκτυο, DB και UI
class ProfileActivity : AppCompatActivity() {
    fun loadProfile() {
        api.getUser() // Κλήση δικτύου
        db.saveUser()    // Εργασία με DB
        updateUI()         // Ενημέρωση UI
    }
}

// ✅ SRP τηρείται: τα επίπεδα διαχωρίστηκαν
class ProfileViewModel : ViewModel() {
    private val repo = UserRepository()
    fun loadProfile() { repo.getUser() }
}

Σημάδια παραβίασης SRP: η κλάση περιέχει πάνω από 200 γραμμές, έχει μεθόδους από διαφορετικούς τομείς, αλλάζει συχνά για διαφορετικούς λόγους. Για την ανάπτυξη Android ο κανόνας είναι απλός: το Activity είναι υπεύθυνο μόνο για τον κύκλο ζωής της οθόνης, το ViewModel — για την κατάσταση UI, το Repository — για τις πηγές δεδομένων.

SRP και αρχιτεκτονική μικρουπηρεσιών

Η αρχή SRP εφαρμόζεται όχι μόνο σε κλάσεις, αλλά και σε αρχιτεκτονική επιπέδου υπηρεσίας. Κάθε μικρουπηρεσία είναι υπεύθυνη για μία οντότητα τομέα: UserService — μόνο χρήστες, PaymentService — μόνο πληρωμές, NotificationService — μόνο ειδοποιήσεις. Αυτό επιτρέπει την κλιμάκωση, ανάπτυξη και δοκιμή υπηρεσιών ανεξάρτητα. Σε μια κινητή εφαρμογή, το SRP σε επίπεδο μικρουπηρεσιών εκδηλώνεται στη διαίρεση των API πελατών ανά τομέα.

OCP: Open-Closed Principle

Open-Closed Principle (OCP) — η αρχή ανοικτού/κλειστού. Οι κλάσεις πρέπει να είναι ανοικτές για επέκταση (μπορεί να προστεθεί νέα συμπεριφορά) και κλειστές για τροποποίηση (ο υπάρχων κώδικας δεν αλλάζει). Επιτυγχάνεται μέσω πολυμορφισμού, αφηρημένων κλάσεων και interfaces. Αντί να προστεθεί if-else σε μια υπάρχουσα μέθοδο, δημιουργείται μια νέα υλοποίηση του interface.

Σύμφωνα με Clean Coder Blog, 2014, το OCP είναι πιο αποτελεσματικό σε συνδυασμό με το πρότυπο Strategy. Για παράδειγμα, αν μια εφαρμογή υποστηρίζει διαφορετικές μεθόδους πληρωμής (Google Pay, Apple Pay, PayPal), δεν χρειάζεται να προστεθεί switch-case στον επεξεργαστή πληρωμών. Κάθε μέθοδος πληρωμής υλοποιεί το κοινό interface PaymentGateway, και ένα νέο σύστημα πληρωμής προστίθεται ως νέα κλάση χωρίς τροποποίηση του υπάρχοντος κώδικα.

kotlin
// ✅ OCP: ανοιχτό για επέκταση, κλειστό για τροποποίηση
interface PaymentGateway {
    fun processPayment(amount: Double): Boolean
}

class GooglePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

// Νέο σύστημα πληρωμών — χωρίς αλλαγή υπάρχοντος κώδικα
class ApplePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

LSP: Liskov Substitution Principle

Liskov Substitution Principle (LSP) — η αρχή αντικατάστασης της Barbara Liskov. Αν το S είναι υποτύπος του T, τότε τα αντικείμενα του T μπορούν να αντικατασταθούν από αντικείμενα S χωρίς να αλλάζουν οι ιδιότητες του προγράμματος. Τυπικά: μια συνάρτηση που χρησιμοποιεί τη βασική κλάση πρέπει να λειτουργεί σωστά με οποιαδήποτε υποκλάση της. Αν η υποκλάση πετάει exception εκεί που η βασική κλάση δεν πετάει — το LSP παραβιάζεται.

Σύμφωνα με Robert C. Martin, 2002, το LSP είναι η πιο δύσκολη στην κατανόηση αρχή SOLID. Κλασικό παράδειγμα παραβίασης — η κλάση Square (τετράγωνο) που κληρονομεί από Rectangle (ορθογώνιο). Αν το setWidth για το Square ορίζει και το πλάτος και το ύψος, ο κώδικας πελάτη που περιμένει συμπεριφορά Rectangle θα λάβει απροσδόκητο αποτέλεσμα. Στην κινητή ανάπτυξη, το LSP παραβιάζεται συχνά κατά την κληρονομικότητα ViewModel, όταν το θυγατρικό ViewModel προσθέτει υποχρεωτικές εξαρτήσεις.

kotlin
// ❌ Παράβαση LSP: Το Square σπάει τη συμπεριφορά του Rectangle
open class Rectangle(open var width: Int, open var height: Int)

class Square(side: Int) : Rectangle(side, side) {
    override var width
        get() = super.width
        set(value) { super.setBoth(value, value) }
}

ISP: Interface Segregation Principle

Interface Segregation Principle (ISP) — η αρχή διαχωρισμού interfaces. Οι πελάτες δεν πρέπει να εξαρτώνται από interfaces που δεν χρησιμοποιούν. Αντί για ένα “παχύ” interface, δημιουργούνται πολλά στενά, εξειδικευμένα interfaces. Αν μια κλάση υλοποιεί ένα interface αλλά μέρος των μεθόδων πετάει UnsupportedOperationException ή παραμένει κενό — αυτό είναι σαφές σημάδι παραβίασης ISP.

Σύμφωνα με DigitalOcean, 2024, το ISP είναι ιδιαίτερα σχετικό στην κινητή ανάπτυξη κατά τον σχεδιασμό ViewModel και Repository. Αντί για ένα UserRepository interface με όλες τις CRUD μεθόδους, είναι καλύτερο να δημιουργήσετε QueryUserRepository (μόνο ανάγνωση) και CommandUserRepository (εγγραφή). Τότε ο αναγνώστης πελάτης (στοιχείο UI) εξαρτάται μόνο από το Query interface και δεν γνωρίζει για τις μεθόδους εγγραφής.

kotlin
// ❌ Παχύ interface — ο πελάτης αναγκάζεται να υλοποιεί περιττές μεθόδους
interface UserOperations {
    fun getUser(id: String): User
    fun saveUser(user: User)
    fun deleteUser(id: String)
    fun exportUsers(): File
}

// ✅ ISP: διαχωρισμένα interfaces
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }

DIP: Dependency Inversion Principle

Dependency Inversion Principle (DIP) — η αρχή αντιστροφής εξαρτήσεων. Τα modules υψηλού επιπέδου δεν πρέπει να εξαρτώνται από modules χαμηλού επιπέδου. Και τα δύο επίπεδα πρέπει να εξαρτώνται από abstractions (interfaces). Τα abstractions δεν πρέπει να εξαρτώνται από λεπτομέρειες — οι λεπτομέρειες εξαρτώνται από abstractions. Αυτό δεν είναι το ίδιο με το “Dependency Injection” (εισαγωγή εξαρτήσεων), αν και το DI είναι ένας συνηθισμένος τρόπος υλοποίησης του DIP.

Σύμφωνα με Robert C. Martin, 2019, το DIP είναι η βάση της Clean Architecture. Το ViewModel (υψηλό επίπεδο) δεν πρέπει να δημιουργεί άμεσα ένα στιγμιότυπο RetrofitApi (λεπτομέρεια). Αντίθετα, το ViewModel εξαρτάται από το UserRepository interface, και η συγκεκριμένη υλοποίηση UserRepositoryImpl με Retrofit μεταβιβάζεται μέσω του κατασκευαστή. Στο Android, το DIP υλοποιείται μέσω Hilt/Dagger ή Koin: όλες οι εξαρτήσεις παρέχονται μέσω του DI container.

kotlin
// ✅ DIP: Το Module εξαρτάται από abstraction, όχι από λεπτομέρεια
class UserRepositoryImpl(
    private val api: UserApi,   // Εξαρτάται από interface
    private val db: UserDao     // Εξαρτάται από interface
) : UserRepository {

    override suspend fun getUser(id: String): User {
        return api.fetchUser(id)
    }
}

// Hilt DI: οι λεπτομέρειες συνδέονται μέσω DI module
@Module
object NetworkModule {
    @Provides
    fun provideUserApi(retrofit: Retrofit): UserApi =
        retrofit.create(UserApi::class.java)
}

Εφαρμογή του SOLID στην κινητή ανάπτυξη

Το SOLID στην κινητή ανάπτυξη εφαρμόζεται σε όλα τα επίπεδα: από την αρχιτεκτονική της εφαρμογής μέχρι μεμονωμένες κλάσεις. Σε Android projects, η Clean Architecture χωρίζει τον κώδικα σε τρία επίπεδα: domain (επιχειρηματική λογική — ανεξάρτητη από frameworks), data (αποθετήρια, API, DB) και presentation (UI, ViewModel). Το επίπεδο domain χρησιμοποιεί τις αρχές SOLID: use case (SRP), interfaces αποθετηρίων (DIP), κλάσεις οντοτήτων (OCP + LSP).

Σύμφωνα με Android Developers Guide, 2025, το SRP στο Android εκδηλώνεται στη διαίρεση ViewModel, Repository και Mapper. OCP — κατά την προσθήκη νέων πηγών δεδομένων μέσω του DataSource interface. LSP — στην ομοιόμορφη επεξεργασία του Result από διαφορετικά αποθετήρια. ISP — στην προσέγγιση CQRS (διαχωρισμός αποθετηρίων Read/Write). DIP — μέσω Hilt/Koin για εισαγωγή εξαρτήσεων.

ΑρχήΠρόβλημα χωρίς αυτήνΛύση σε κινητό project
SRPActivity 1000+ γραμμώνViewModel + UseCase + Repository
OCPswitch-case ανά τύπο πληρωμήςStrategy: PaymentGateway interface
LSPΣφάλμα κατά αντικατάσταση BaseViewModelΈλεγχος σύμβασης υποκλάσεων
ISPUnsupportedOperationExceptionΔιαχωρισμός Reader / Writer
DIPViewModel δημιουργεί χειροκίνητα RetrofitDI container Hilt / Koin

Συνηθισμένα λάθη κατά την εφαρμογή του SOLID

Λάθη SOLID συνήθως σχετίζονται με υπερβολική περιπλοκή του κώδικα. Πρώτο — κυριολεκτική τήρηση των αρχών χωρίς να λαμβάνεται υπόψη το πλαίσιο. Η διαίρεση μιας κλάσης UserService σε 10 interfaces και 15 κλάσεις για “καθαρό” ISP είναι υπερσχεδιασμός. Το SOLID είναι εργαλείο, όχι σκοπός. Δεύτερο λάθος — σύγχυση μεταξύ SRP και “μία μέθοδος = μία ευθύνη”. Μια κλάση μπορεί να έχει πολλές μεθόδους, αν όλες ανήκουν σε μία περιοχή ευθύνης.

Σύμφωνα με Simple Thread, 2024, τρίτο λάθος — αγνόηση του LSP κατά την κληρονομικότητα ViewModel στο Android. Αν το βασικό ViewModel περιμένει LiveData και το θυγατρικό χρησιμοποιεί StateFlow — ο κώδικας πελάτη που είναι εγγεγραμμένος στο LiveData δεν θα λάβει ενημερώσεις. Τέταρτο — παραβίαση DIP για χάρη δοκιμών: το RepositoryImpl δημιουργεί άμεσα ένα στιγμιότυπο OkHttpClient, καθιστώντας αδύνατες τις μοναδιαίες δοκιμές.

Χρυσός κανόνας: εφαρμόστε το SOLID όταν λύνει ένα πραγματικό πρόβλημα (συχνές αλλαγές, δυσκολία δοκιμών, επανάληψη). Για απλές οθόνες CRUD, η αυστηρή τήρηση και των πέντε αρχών είναι υπερβολή. Για επιχειρηματική λογική, οικονομικούς υπολογισμούς και αλληλεπιδράσεις API, το SOLID είναι υποχρεωτικό.

Σύνδεση SOLID και Clean Architecture

Clean Architecture (Robert C. Martin, 2012) — άμεση εφαρμογή του SOLID σε επίπεδο επιπέδων εφαρμογής. Το SRP καθορίζει τα όρια use case (κάθε use case — μία κλάση). Το OCP υλοποιείται μέσω interfaces αποθετηρίων (το επίπεδο Data μπορεί να αλλάξει χωρίς να τροποποιηθεί το Domain). Το ISP παρέχει τον διαχωρισμό του Use Case σε input/output boundary. Το DIP — την κατεύθυνση εξαρτήσεων προς τα μέσα του επιπέδου Domain. Το LSP εγγυάται ότι κάθε υλοποίηση αποθετηρίου είναι αντικαταστάσιμη χωρίς να σπάει το use case.

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

Τι είναι το SOLID με απλά λόγια;

SOLID — πέντε κανόνες γραφής κώδικα για να είναι εύκολο να αλλάξει, να δοκιμαστεί και να κατανοηθεί. Κάθε γράμμα είναι μία αρχή: μην γράφετε μεγάλες κλάσεις (SRP), μην αλλάζετε τον υπάρχοντα κώδικα — προσθέστε νέο (OCP), μην σπάτε τη συμπεριφορά των κληρονόμων (LSP) και άλλα.

Ποια αρχή SOLID είναι η πιο σημαντική;

SRP (Single Responsibility) θεωρείται η πιο σημαντική επειδή η παραβίασή της οδηγεί σε God-classes — τεράστιες κλάσεις που είναι δύσκολο να δοκιμαστούν και να αλλάξουν. Ωστόσο, χωρίς DIP (Dependency Inversion) ο κώδικας παραμένει σφιχτά συνδεδεμένος, που είναι επίσης κρίσιμο.

Είναι το SOLID υποχρεωτικό για κινητή ανάπτυξη;

Δεν είναι υποχρεωτικό, αλλά συνιστάται ιδιαίτερα για εμπορικά projects με μεγάλο κύκλο ζωής. Για απλές εφαρμογές (μία οθόνη, χωρίς επιχειρηματική λογική) το SOLID μπορεί να είναι υπερβολικό. Για projects με 50+ οθόνες και 3+ προγραμματιστές, το SOLID είναι το απαραίτητο ελάχιστο.

Τι συμβαίνει αν δεν τηρώ το SOLID;

Συνέπειες: οι κλάσεις γίνονται “παχιές” (1000+ γραμμές), μια αλλαγή σε ένα μέρος σπάει τρία άλλα, είναι αδύνατο να γραφούν μοναδιαίες δοκιμές, η προσθήκη νέας λειτουργίας παίρνει εβδομάδες αντί για ημέρες. Με τον καιρό, ο κώδικας μετατρέπεται σε “Big Ball of Mud” — μπερδεμένος και εύθραυστος.

Πώς ελέγχω αν το SOLID τηρείται στο project;

Σημάδια τήρησης: κάθε κλάση έχει λιγότερες από 200 γραμμές, μια αλλαγή λειτουργίας δεν επηρεάζει 5+ αρχεία, οι δοκιμές γράφονται χωρίς mocking 10 εξαρτήσεων, ένας νέος προγραμματιστής κατανοεί τη δομή σε μία μέρα. Εργαλεία όπως SonarQube και detekt βοηθούν στον εντοπισμό παραβιάσεων SRP και DIP.

Περίληψη

  • SOLID — πέντε αρχές OOP (SRP, OCP, LSP, ISP, DIP) για ευέλικτο και συντηρήσιμο κώδικα
  • SRP — κάθε οντότητα είναι υπεύθυνη για μία εργασία, λύνει το πρόβλημα God-classes
  • OCP — επέκταση μέσω πολυμορφισμού, όχι τροποποίηση υπάρχοντος κώδικα
  • LSP — οι κληρονόμοι δεν πρέπει να σπάνε τη συμπεριφορά της βασικής κλάσης
  • ISP — στενά interfaces αντί για καθολικό “ελβετικό σουγιά”
  • DIP — εξάρτηση από abstractions, εισαγωγή μέσω Hilt/Koin στο Android
  • SOLID είναι υποχρεωτικό για Clean Architecture και εμπορικά κινητά projects

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

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

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

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