Dependency Injection: τι είναι, εισαγωγή εξαρτήσεων σε iOS και Android

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

Dependency Injection (DI, εισαγωγή εξαρτήσεων) — τεχνική κατά την οποία ένα αντικείμενο λαμβάνει τις εξαρτήσεις του από έξω, αντί να τις δημιουργεί το ίδιο. Το DI αποτελεί υλοποίηση της αρχής IoC (Inversion of Control) και βρίσκεται στη βάση των Dagger, Hilt και Swinject. Η εισαγωγή εξαρτήσεων μειώνει τη σύζευξη του κώδικα, απλοποιεί τη δοκιμή και καθιστά την αρχιτεκτονική ευέλικτη. Στο Android το DI είναι πρότυπο μέσω του Dagger Hilt της Google, στο iOS — μέσω Swinject ή χειροκίνητης εισαγωγής. Περισσότερα — στο Android DI Guide.

Κύρια σημεία

  • Dependency Injection — οι εξαρτήσεις μεταφέρονται στο αντικείμενο από έξω, δεν δημιουργούνται εσωτερικά
  • Inversion of Control — το DI υλοποιεί την αρχή IoC, ο έλεγχος ροής μεταφέρεται στο δοχείο
  • Dagger Hilt — το πρότυπο DI για Android, βασισμένο στο Dagger της Google
  • Swinject — δημοφιλές πλαίσιο DI για iOS και Swift
  • Μείωση σύζευξης — η κλάση εξαρτάται από αφαιρέσεις, όχι από συγκεκριμένες υλοποιήσεις

Τι είναι το Dependency Injection: ουσία και τύποι DI

Dependency Injection — τεχνική κατά την οποία ένα αντικείμενο λαμβάνει εξαρτήσεις (υπηρεσίες, αποθετήρια, διαμορφώσεις) μέσω κατασκευαστή, setter ή διεπαφής, αντί να τις δημιουργεί το ίδιο με new. Σκοπός του DI είναι η μείωση της σύζευξης (coupling) μεταξύ κλάσεων. Αν μια κλάση δημιουργεί η ίδια τις εξαρτήσεις της, είναι άκαμπτα δεμένη με συγκεκριμένες υλοποιήσεις, γεγονός που δυσχεραίνει τη δοκιμή και την τροποποίηση. Στο DI η κλάση εργάζεται με μια αφαίρεση (protocol/interface) και η συγκεκριμένη υλοποίηση παρέχεται από έξω.

Τρεις τρόποι εισαγωγής — Constructor Injection (μέσω init/constructor), Setter Injection (μέσω ιδιότητας/setter), Interface Injection (μέσω μεθόδου διεπαφής). Constructor Injection — ο προτιμώμενος τρόπος: οι εξαρτήσεις είναι εμφανώς ορατές στην υπογραφή, το αντικείμενο δημιουργείται πάντα σε έγκυρη κατάσταση. Το Setter Injection χρησιμοποιείται για προαιρετικές εξαρτήσεις με προεπιλεγμένη τιμή. Interface Injection — σπάνια, κυρίως για δοχεία DI.

Τύπος DIΤρόποςΠότε να χρησιμοποιείταιΠαράδειγμα
ConstructorΠαράμετροι αρχικοποιητήΥποχρεωτικές εξαρτήσειςinit(service: ServiceProtocol)
PropertyΙδιότητα κλάσηςΠροαιρετικές εξαρτήσειςvar service: ServiceProtocol?
MethodΠαράμετρος μεθόδουΠροσωρινές εξαρτήσειςfunc doWork(with service: Service)

Δοχείο DI — βιβλιοθήκη που διαχειρίζεται τη δημιουργία και τον κύκλο ζωής των εξαρτήσεων. Το δοχείο περιέχει καταγραφή τύπων (κάθε αφηρημένος τύπος αντιστοιχίζεται σε μια συγκεκριμένη υλοποίηση) και εργοστάσιο για τη δημιουργία αντικειμένων με επιλυμένες εξαρτήσεις. Στο Android — Dagger/Hilt, στο iOS — Swinject, Needle, Dip. Το δοχείο μπορεί να διαχειρίζεται εύρος (Scope): singleton (μία εμφάνιση ανά εφαρμογή), εύρος λειτουργίας (ανά οθόνη) ή νέο αντικείμενο σε κάθε αίτημα.

Dagger Hilt: DI για Android με παραγωγή κώδικα

Dagger Hilt — στρώμα πάνω από το Dagger της Google, η τυπική βιβλιοθήκη DI για Android. Το Hilt απλοποιεί το Dagger: αφαιρεί τη χειροκίνητη δημιουργία συστατικών, προσθέτει @HiltAndroidApp, @AndroidEntryPoint και @Module. Το Hilt ενσωματώνεται με τον κύκλο ζωής του Android: ViewModel, Activity, Fragment, Service, BroadcastReceiver μπορούν να λαμβάνουν εξαρτήσεις μέσω σχολιασμών. Η παραγωγή κώδικα γίνεται στο στάδιο μεταγλώττισης — το Dagger δημιουργεί υλοποιήσεις συστατικών, δίνοντας μηδενική επιβάρυνση runtime.

kotlin
// Application class
@HiltAndroidApp
class MyApp : Application()

// Module — καθορίζει πώς να δημιουργούνται οι εξαρτήσεις
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()

    @Provides
    @Singleton
    fun provideApiService(client: OkHttpClient): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .client(client)
            .build()
            .create(ApiService::class.java)
    }
}

// ViewModel λαμβάνει την εξάρτηση μέσω κατασκευαστή
@HiltViewModel
class MainViewModel @Inject constructor(
    private val apiService: ApiService
) : ViewModel() {
    private val _state = MutableStateFlow(MainState.Loading)
    val state: StateFlow<MainState> = _state.asStateFlow()

    fun loadData() {
        viewModelScope.launch {
            _state.value = MainState.Success(apiService.getData())
        }
    }
}

// Activity — @AndroidEntryPoint ενεργοποιεί το DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
}

Συστατικά Dagger και εύρη ορατότητας — @Singleton (για ολόκληρη την εφαρμογή), @ActivityScoped (για Activity), @FragmentScoped (για Fragment), @ViewModelScoped (για ViewModel). Η επιλογή εύρους καθορίζει τη διάρκεια ζωής του αντικειμένου. @Singleton — μία εμφάνιση ανά διεργασία, κατάλληλο για OkHttpClient και βάση δεδομένων. @ActivityScoped — το αντικείμενο ζει όσο ζει το Activity, για εξαρτήσεις οθόνης. @ViewModelScoped — νέο στο Hilt 2.45+, το αντικείμενο ζει όσο ζει το ViewModel, βολικό για εύρη coroutine.

Swinject: DI για iOS σε Swift

Swinject — δημοφιλές πλαίσιο DI για iOS με ανοιχτό κώδικα. Το Swinject παρέχει Container, Assemblies και διάφορα εύρη. Σε αντίθεση με το Dagger, το Swinject λειτουργεί σε runtime — οι εξαρτήσεις επιλύονται δυναμικά χωρίς παραγωγή κώδικα. Αυτό καθιστά το Swinject απλούστερο στη διαμόρφωση, αλλά δυσκολεύει τον εντοπισμό σφαλμάτων: το σφάλμα μη επιλυμένης εξάρτησης εμφανίζεται μόνο σε runtime. Το Swinject υποστηρίζει Constructor Injection, Property Injection και Method Injection.

swift
import Swinject

// Assembly — ομάδα καταγραφών
class NetworkAssembly: Assembly {
    func assemble(container: Container) {
        container.register(NetworkServiceProtocol.self) { _ in
            NetworkService()
        }.inObjectScope(.container) // singleton

        container.register(UserRepositoryProtocol.self) { r in
            UserRepository(
                networkService: r.resolve(NetworkServiceProtocol.self)!
            )
        }
    }
}

// ViewModel μέσω Constructor Injection
class ProfileViewModel: ObservableObject {
    private let repository: UserRepositoryProtocol

    init(repository: UserRepositoryProtocol) {
        self.repository = repository
    }

    @Published var user: User?
    func loadUser() {
        repository.fetchUser { [weak self] user in
            self?.user = user
        }
    }
}

// Διαμόρφωση DI στο AppDelegate ή App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!

// Property Injection για UIKit ViewController
container.register(UserViewController.self) { r in
    let vc = UserViewController()
    vc.viewModel = r.resolve(ProfileViewModel.self)
    return vc
}

Εύρη Swinject — .transient (νέο αντικείμενο κάθε φορά), .container (singleton ανά δοχείο), .graph (προεπιλογή — το αντικείμενο μοιράζεται εντός ενός γράφου εξαρτήσεων). Για εφαρμογές iOS, τα .container και .transient είναι επαρκή. Το Swinject υποστηρίζει επίσης Assembler — ομαδοποίηση Assembly για αρθρωτή αρχιτεκτονική. Για δοκιμές, τα Assembly αντικαθίστανται με MockAssembly, επιτρέποντας την αντικατάσταση εξαρτήσεων χωρίς αλλαγή του κώδικα παραγωγής.

Σύγκριση DI με Service Locator και χειροκίνητη εισαγωγή

DI vs Service Locator — και τα δύο πρότυπα λύνουν το πρόβλημα διαχείρισης εξαρτήσεων, αλλά με διαφορετικό τρόπο. Το DI εισάγει εξαρτήσεις στο αντικείμενο, το Service Locator παρέχει ένα καθολικό μητρώο από το οποίο το αντικείμενο ζητά μόνο του τις εξαρτήσεις. Το DI δηλώνει ρητά τις εξαρτήσεις μέσω κατασκευαστή (ή setter). Το Service Locator κρύβει τις εξαρτήσεις — ζητούνται εντός της μεθόδου, καθιστώντας την υπογραφή λιγότερο πληροφοριακή. Το DI δοκιμάζεται ευκολότερα: αρκεί να τοποθετηθεί mock στον κατασκευαστή. Το Service Locator απαιτεί διαμόρφωση του καθολικού μητρώου για κάθε δοκιμή.

ΧαρακτηριστικόDependency InjectionService LocatorΧειροκίνητη εισαγωγή
Σαφήνεια εξαρτήσεωνΣτον κατασκευαστήΚρυμμένες στο σώμα μεθόδουΡητές
ΔοκιμήMock στον κατασκευαστήΔιαμόρφωση LocatorMock στον κατασκευαστή
Πολυπλοκότητα διαμόρφωσηςΑπαιτεί δοχείο DIΚαθολικό μητρώοΧειροκίνητη δημιουργία
Επιβάρυνση runtimeDagger — compile-timeRuntime lookupΚαμία

DI vs χειροκίνητη εισαγωγή — χωρίς δοχείο DI, οι εξαρτήσεις δημιουργούνται χειροκίνητα σε εργοστάσια ή AppDelegate. Για 5-10 κλάσεις, η χειροκίνητη εισαγωγή είναι απλούστερη — δεν απαιτεί εκμάθηση Dagger ή Swinject. Για 50+ κλάσεις, η χειροκίνητη εισαγωγή γίνεται πρόβλημα: κατασκευαστές με 5-6 παραμέτρους, περίπλοκη σειρά δημιουργίας, επανάληψη κώδικα. Το δοχείο DI αυτοματοποιεί αυτές τις διαδικασίες και παρέχει σαφή κύκλο ζωής. Χειροκίνητη εισαγωγή χωρίς δοχείο — καλή επιλογή για μικρά έργα και πρωτότυπα.

Βέλτιστες πρακτικές Dependency Injection

Constructor Injection — πρότυπο. Να χρησιμοποιείτε πάντα Constructor Injection για υποχρεωτικές εξαρτήσεις. Αυτό καθιστά τις εξαρτήσεις ρητές και το αντικείμενο πάντα έτοιμο για εργασία. Setter Injection — μόνο για προαιρετικές εξαρτήσεις (π.χ., delegate ή listener). Interface Injection — μην το χρησιμοποιείτε, εκτός αν γράφετε τη δική σας βιβλιοθήκη DI. Constructor Injection — ο μόνος τρόπος να εγγυηθείτε ότι το αντικείμενο δημιουργείται σε έγκυρη κατάσταση.

Μία κλάση — μία ευθύνη. Αν ο κατασκευαστής μιας κλάσης απαιτεί 5+ παραμέτρους, πιθανότατα η κλάση παραβιάζει την αρχή Single Responsibility Principle. Χωρίστε την κλάση σε πολλές με λιγότερες εξαρτήσεις. Σημάδι: αν γράφετε μια κλάση ServiceManager με 6 διαφορετικές υπηρεσίες — αυτό είναι το αντ-πρότυπο God Object. Μεταφέρετε την επιχειρηματική λογική σε Use Cases (Interactors), το καθένα με 1-2 εξαρτήσεις.

kotlin
// ❌ Κακό: 6 εξαρτήσεις — God Object
class ProfileViewModel @Inject constructor(
    private val api: ApiService,
    private val db: Database,
    private val analytics: Analytics,
    private val prefs: Preferences,
    private val location: LocationProvider,
    private val notification: NotificationManager
)

// ✅ Καλό: Use Cases με 1-2 εξαρτήσεις
class ProfileViewModel @Inject constructor(
    private val loadProfileUseCase: LoadProfileUseCase,
    private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)

Εύρος και κύκλος ζωής — επιλέξτε το σωστό εύρος για κάθε εξάρτηση. Singleton: OkHttpClient, βάση δεδομένων, SharedPreferences. Εύρος λειτουργίας: αποθετήρια, Use Cases (αν δεν έχουν κατάσταση). Transient: Value Objects, DateFormatter, αναλυτές. Σφάλμα εύρους — συχνό πρόβλημα: ένα singleton που αποθηκεύει κατάσταση οθόνης οδηγεί σε διαρροές μνήμης. Στο Android Hilt το @ActivityScoped λύνει αυτό το πρόβλημα, στο Swinject — το .container με προσοχή.

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

Γιατί χρειάζεται το DI αν μπορούμε απλά να δημιουργούμε με new;

Το new δημιουργεί άκαμπτη σύνδεση μεταξύ κλάσεων — δεν μπορείτε να αντικαταστήσετε την υλοποίηση χωρίς αλλαγή κώδικα. Είναι δύσκολο να δοκιμάσετε: δεν μπορείτε να βάλετε mock αντί για την πραγματική υπηρεσία. Το SRP παραβιάζεται: η κλάση είναι υπεύθυνη τόσο για την επιχειρηματική λογική όσο και για τη δημιουργία εξαρτήσεων. Το DI λύνει αυτά τα προβλήματα μέσω εισαγωγής εξαρτήσεων από έξω και εργασίας με αφαιρέσεις.

Dagger Hilt ή Koin — τι να επιλέξουμε για Android;

Dagger Hilt — το πρότυπο της Google, compile-time DI με παραγωγή κώδικα, καλύτερη απόδοση και ενσωμάτωση με Jetpack. Koin — runtime DI, απλούστερο στη διαμόρφωση, αλλά πιο αργό και με σφάλματα runtime. Επιλέξτε Hilt για έργα παραγωγής. Το Koin είναι κατάλληλο για πρωτότυπα και μικρές εφαρμογές.

Είναι το Swinject το μοναδικό DI για iOS;

Όχι. Για iOS είναι διαθέσιμα: Swinject (runtime, δημοφιλές), Needle (compile-time από την Uber), Dip (ελαφρύ), Weaver (βασισμένο σε Sourcery). Η Apple δεν παρέχει ενσωματωμένο δοχείο DI, αλλά η χειροκίνητη εισαγωγή μέσω init είναι συνήθης πρακτική. Για SwiftUI, συχνά αρκεί το χειροκίνητο DI μέσω Environment ή @StateObject χωρίς εξωτερικές βιβλιοθήκες.

Μπορεί να χρησιμοποιηθεί το DI χωρίς πλαίσιο;

Ναι. Η χειροκίνητη εισαγωγή μέσω κατασκευαστή — είναι DI χωρίς πλαίσιο. Service Locator — εναλλακτική χωρίς πλαίσιο. Εργοστάσια και Factory Method — επίσης μορφή DI. Το πλαίσιο (Dagger, Swinject) αυτοματοποιεί τη συνήθη καταγραφή και επίλυση εξαρτήσεων, αλλά για 10-20 κλάσεις το χειροκίνητο DI είναι επαρκές.

Το DI είναι πρότυπο ή αρχή;

Το DI — είναι μια τεχνική (πρότυπο) που υλοποιεί την αρχή Inversion of Control. Σε αντίθεση με τα πρότυπα GoF, το DI δεν έχει αυστηρή δομή 3-4 κλάσεων. Το DI είναι τρόπος οργάνωσης εξαρτήσεων, όχι σχεδιαστικό πρότυπο. Τα δοχεία DI (Dagger, Swinject) είναι πλαίσια που αυτοματοποιούν αυτή την τεχνική.

Σύνοψη

  • DI — τεχνική εισαγωγής εξαρτήσεων από έξω μέσω κατασκευαστή, setter ή μεθόδου
  • Dagger Hilt — πρότυπο DI για Android με compile-time παραγωγή κώδικα και @HiltViewModel
  • Swinject — runtime DI για iOS με Container, Assembly και εύρη
  • Constructor Injection — ο προτιμώμενος τρόπος για υποχρεωτικές εξαρτήσεις
  • Εύρος — Singleton για υπηρεσίες χωρίς κατάσταση, εύρος λειτουργίας για εξαρτήσεις οθόνης
  • Δοκιμή — το DI απλοποιεί την αντικατάσταση εξαρτήσεων με mock χωρίς αλλαγή κώδικα

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

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

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

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