DIP: geliştirmede bağımlılıkların tersine çevrilmesinin temelleri

Yazar: IT Sectr Yayınlanma: 2026-05-12 Okuma süresi: 9 dk

DIP (Dependency Inversion Principle) — modüller arasındaki bağımlılıkların oluşturulma kurallarını tanımlayan SOLID'in beşinci ilkesidir: üst düzey modüller alt düzey modüllere bağımlı olmamalı, her ikisi de soyutlamalara bağımlı olmalıdır. Soyutlamalar ayrıntılara bağımlı olmamalı — ayrıntılar soyutlamalara bağımlı olmalıdır. Robert Martin tarafından Clean Architecture (2017)'de açıklanan bu ilke, gevşek bağlı mimarinin temelini oluşturur. Bu kitaba göre, bağımlılıkların tersine çevrilmesi ilkesi uygulama katmanları arasındaki katı bağları ortadan kaldırır.

Önemli Noktalar

  • DIP — bağımlılıkların tersine çevrilmesi ilkesi, SOLID'de beşinci, mimari sınırlar hakkında
  • Üst düzey modüller alt düzey modülleri import etmemeli — yalnızca soyutlamalar
  • DIP ≠ DI: Dependency Inversion mimari bir ilkedir, Dependency Injection onu uygulamanın bir yoludur
  • Soyutlamalar üst düzey modüle aittir, uygulamalar alt düzey modüle aittir
  • DIP çok katmanlı mimarilerde geleneksel bağımlılık hiyerarşisini tersine çevirir

DIP (Dependency Inversion Principle) Nedir?

DIP (Dependency Inversion Principle), modüller arasındaki bağımlılık yönüne ilişkin geleneksel görüşü tersine çeviren bağımlılıkların tersine çevrilmesi ilkesidir. Üst düzey modüller (iş mantığı) doğrudan alt düzey modüllere (veritabanı, ağ, UI) bağımlı olmamalıdır. Bunun yerine, her iki düzey de üst düzey modülde tanımlanan soyutlamalara bağımlıdır.

DIP'nin resmi formülasyonu iki kural içerir: A — üst düzey modüller alt düzey modüllere bağımlı olmamalı, her ikisi de soyutlamalara bağımlı olmalıdır. B — soyutlamalar ayrıntılara bağımlı olmamalı, ayrıntılar soyutlamalara bağımlı olmalıdır. İkinci kural birinciden türetilir: eğer bir soyutlama ayrıntılara bağımlıysa, üst düzey bir modül için istikrarlı bir temel olamaz.

DIP olmadan, tipik bir mimari şöyle görünür: BusinessLogic → DatabaseRepository — iş mantığı doğrudan somut bir depoya bağımlıdır. DIP ile: BusinessLogic → DatabaseServiceInterface ← DatabaseRepository. BusinessLogic, DatabaseRepository'nin varlığını bilmez, yalnızca iş mantığının dışında uygulanan DatabaseService arayüzünü bilir.

DIP'de Bağımlılık Yönü

Tersine çevirme, kontrol akışı ve bağımlılık akışının zıt yönlerde gitmesi anlamına gelir. Kontrol akışı yukarıdan aşağıya gider: UI → ViewModel → UseCase → Repository. Bağımlılık akışı aşağıdan yukarıya gider: Repository, UseCase'de tanımlanan bir arayüzü uygular. Repository (alt düzey), UseCase'ye (üst düzey) bağımlıdır.

Bu tersine çevirme, DIP ile sıradan katman ayırımı arasındaki temel farktır. Geleneksel katmanlı mimaride, her katman altındaki katmana bağımlıdır. DIP'li mimaride, tüm katmanlar soyutlamalara bağımlıdır, bu soyutlamaların uygulaması ise altyapı katmanında bulunur ve DI mekanizmaları aracılığıyla üst katmanlara “bağlanır”.

Bağımlılıkların Tersine Çevrilmesi İlkesi Nasıl Çalışır

DIP mekanizması, üst düzey modüllerde soyutlamalar tanımlanarak ve alt düzey modüllerde bunların uygulanmasıyla gerçekleştirilir. Üst düzey modül, ihtiyaç duyduğu işlevsellik için bir arayüz bildirir. Alt düzey modül bu arayüzü uygular. Kablolama (wiring), uygulamanın kompozisyon kökünde gerçekleşir.

Mevcut koda DIP ekleme süreci: alt düzey modül için bir arayüz çıkarın, bu arayüzü üst düzey modüle (veya ayrı bir soyutlama katmanına) taşıyın, üst düzey modülün bağımlılığını arayüzü kullanacak şekilde yeniden yazın, alt düzey modülün bu arayüzü uygulamasını sağlayın. Bu adımlardan sonra, bağımlılık yönü tersine çevrilmiştir.

DIP, bir kompozisyon kökü mekanizması gerektirir — uygulamada tüm bağımlılıkların oluşturulduğu ve birbirine bağlandığı bir nokta. Android'de bu, Application.get() veya Hilt bileşenidir, iOS'te — AppDelegate veya SceneDelegate. Kompozisyon kökü, kodun somut uygulamaları bildiği tek yerdir.

DIP Yoluyla Katman İzolasyonu

DIP, uygulama katmanları arasında mimari sınırlar oluşturur. ViewModel, UserRepository arayüzüne bağımlı olduğunda, sunum ve domain katmanları arasında bir sınır oluşur: ViewModel (sunum), verilerin nereden geldiğini bilmez. Bu sınır, ViewModel'i etkilemeden UserRepository uygulamasının (Room → REST → Mock) değiştirilmesine izin verir. Bu tür sınırlar ne kadar çoksa, uygulama çerçeve ve kütüphane değişikliklerine karşı o kadar dirençlidir.

Google tarafından önerilen Android mimarisinde DIP, domain katmanında bulunan ve Repository arayüzlerine bağımlı olan UseCase'ler aracılığıyla uygulanır. RepositoryImpl, veri katmanındadır ve bu arayüzleri uygular. Sunum katmanı (ViewModel), UseCase'lere bağımlıdır. Bağımlılık yönü sunumdan domaine, domainden veriye gider — ancak hiçbir katman diğer bir katmanın somut uygulamalarını bilmez.

DIP ve DI (Dependency Injection) Arasındaki Fark

DIP ve DI sıklıkla karıştırılır, ancak farklı kavramlardır. DIP mimari bir ilkedir (NE yapılmalı: soyutlamalara bağımlı olmak). DI bir uygulama modelidir (NASIL yapılmalı: bağımlılıkları kurucu üzerinden iletmek). DIP “modüller neye dayanmalı?” sorusunu yanıtlar, DI ise “nesneler bağımlılıklarını nasıl alır?” sorusunu yanıtlar.

Dependency Injection, bir nesneye kurucu, yöntem veya özellik aracılığıyla bağımlılıkları enjekte etmenin bir yoludur. Bir Kotlin sınıfı, kurucusu aracılığıyla bir Repository arayüzü aldığında — bu DI'dir. Bir ViewModel sınıfının somut bir RoomRepository uygulaması yerine Repository arayüzüne bağımlı olması — bu DIP'dir. DI araçtır, DIP hedeftir.

DI çerçevesi olmadan DIP'i takip edebilirsiniz: kompozisyon kökünde bağımlılıkların manuel olarak kablolanması da DI'dir (manuel DI). DIP'i ihlal ederek bir DI çerçevesi (Dagger, Hilt, Koin) kullanabilirsiniz: ViewModel doğrudan new() aracılığıyla bir Repository nesnesi oluşturursa — çerçeve kurulu olsa bile DIP ihlal edilmiştir. DIP mimari bir karardır, DI teknik bir ayrıntıdır.

Mobil Geliştirmede DIP Örnekleri

Veri katmanına DIP uygulamasının bir Android örneğine bakalım. DIP olmadan, bir ViewModel doğrudan RoomDatabase ve DAO oluşturur. DIP ile — ViewModel, UserRepository arayüzüne bağımlıdır ve somut RoomUserRepository uygulaması dışarıdan sağlanır.

kotlin
// Soyutlama domain katmanına (üst düzey) aittir
interface UserRepository {
    fun getUser(id: Int): User
}

// Domain katmanı yalnızca soyutlamaya bağımlıdır
class GetUserUseCase(
    private val repo: UserRepository
) {
    fun execute(id: Int): User = repo.getUser(id)
}

// Veri katmanındaki uygulama, domain katmanı soyutlamasına bağımlıdır
class RoomUserRepository(
    private val dao: UserDao
) : UserRepository {
    override fun getUser(id: Int): User {
        return dao.getById(id)
    }
}

// Kompozisyon kökü
class AppModule {
    fun provideUserRepository(dao: UserDao): UserRepository {
        return RoomUserRepository(dao)
    }
}

Bir Application Coordinator ve navigasyon protokolü ile iOS örneği:

swift
// Domain katmanında navigasyon soyutlaması
protocol AuthNavigation {
    func navigateToHome()
    func navigateToLogin()
}

// ViewModel soyutlamaya bağımlıdır, UIKit'e değil
final class AuthViewModel {
    private let navigation: AuthNavigation

    init(navigation: AuthNavigation) {
        self.navigation = navigation
    }

    func onLoginSuccess() {
        navigation.navigateToHome()
    }
}

// Coordinator (UIKit katmanı) domain katmanı protokolünü uygular
final class AppCoordinator: AuthNavigation {
    func navigateToHome() {
        // UIKit navigasyon kodu
    }
    func navigateToLogin() {
        // UIKit navigasyon kodu
    }
}

Kilit nokta: AuthViewModel (domain), AppCoordinator'ın (UIKit) varlığını bilmez. Yalnızca AuthNavigation protokolünü bilir. Yarın UIKit, SwiftUI ile değiştirilirse — AuthViewModel değişiklik gerektirmez. DIP, domain katmanını UI çerçevelerinden ve kütüphanelerinden bağımsız hale getirir.

DIP İçin Araçlar: Dagger, Hilt, Koin

Hilt, Android için standart DI aracıdır ve Google tarafından önerilir. Jetpack'e entegredir, ViewModel, Fragment, Service ve diğer Android bileşenlerini destekler. Hilt, @Module, @Provides, @Inject ek açıklamaları aracılığıyla kompozisyon kökü oluşturmayı otomatikleştirir. Hilt kullanmak DIP uyumluluğunu garanti etmez — UserRepository arayüzü veri katmanında değil, domain katmanında tanımlanmalıdır.

Koin, kod oluşturma veya ek açıklama işleme olmadan Kotlin için hafif bir DI çerçevesidir. Koin'in DSL'si (module, single, factory) öğrenmesi daha kolaydır, ancak bağımlılık denetimi derleme zamanında değil çalışma zamanında gerçekleşir. Koin, iOS desteği sayesinde çok platformlu projelerde (KMP) popülerdir.

Dagger 2, Hilt'in öncülüdür ve hala büyük projelerde kullanılmaktadır. Dagger, derleme zamanında DI kodu üretir ve maksimum performans ve derleme zamanında hata teşhisi sağlar. Hilt, Dagger üzerine inşa edilmiştir ve basitleştirilmiş bir API sağlar. Yeni projeler için Google, Hilt'i birincil DI çerçevesi olarak önerir.

DI Modüllerinin Katmanlara Göre Düzenlenmesi

DI modülleri mimari katmanlara karşılık gelmeli ve DomainModule, DataModule, PresentationModule olarak ayrılmalıdır. DomainModule yalnızca soyutlamalar ve UseCase'ler sağlar. DataModule, soyutlamalar için uygulamalar sağlar. PresentationModule, ViewModel'leri UseCase'lere bağlar. Bu düzenleme, domain katmanının altyapı kütüphanelerinden bağımsız kalmasını sağlar.

DI çerçeveleri arasında geçiş yaparken (örneğin, Koin'den Hilt'e), DomainModule'un yapısı değişmez — yalnızca DataModule ve PresentationModule'daki kablolama yöntemleri değişir. DIP, domain mantığının izolasyonunu sağlarken, DI çerçevesi teknik bir kablolama mekanizmasıdır.

Sıkça Sorulan Sorular

DIP her zaman uygulanmalı mı?

DIP, mimari sınırlarda gereklidir — uygulama katmanları arasında (domain → veri, sunum → domain). Tek bir katman içinde DIP aşırı olabilir. Örneğin, domain katmanı içindeki bir yardımcı sınıf StringFormatter, değiştirilmesi için bir neden yoksa bir arayüz gerektirmez.

DIP, bağımlılık enjeksiyonu ile aynı şey midir?

Hayır. DIP bir ilkedir: modüller soyutlamalara bağımlı olmalıdır. DI bir modeldir: bir nesne bağımlılıklarını kendisi oluşturmak yerine dışarıdan alır. DI, DIP'i uygulamanın bir yoludur, ancak DIP, DI olmadan da (fabrikalar veya hizmet bulucu aracılığıyla) takip edilebilir. DI olmadan DIP mümkündür ancak mimari değeri yoktur.

DIP için arayüzler nerede tanımlanmalı?

Arayüzler, onları kullanan modüle aittir, onları uygulayan modüle değil. UserRepository, domain katmanında bildirilir ve veri katmanında uygulanır. Bu, DIP'in temel kuralıdır: soyutlamanın sahibi tüketicidir, uygulamanın sağlayıcısı değil.

DIP test etmeyi nasıl etkiler?

DIP, izole katmanlarda test etmeyi mümkün kılar. UserRepository'ye (arayüz) bağımlı olan bir ViewModel, veritabanı olmadan bir mock uygulamasıyla test edilebilir. DIP olmadan, ViewModel RoomUserRepository'ye bağımlı olur ve her test için veritabanı kurulumu gerektirirdi. DIP + DI, test sırasında tam modül izolasyonu sağlar.

Android için hangi DI çerçevesi seçilmeli?

Hilt, Android projeleri için standart seçimdir ve Google tarafından önerilir. Koin, Kotlin Multiplatform projeleri için bir alternatiftir. Dagger 2, Hilt'e geçişin haklı olmadığı mevcut projeler içindir. Çerçeve seçimi, mimari düzeyde DIP'i takip etme gerekliliğini ortadan kaldırmaz.

Özet

  • DIP (Dependency Inversion Principle) — soyutlamalar aracılığıyla mimari sınırlar hakkında SOLID'in beşinci ilkesi
  • Üst düzey modüller alt düzey modüllere bağımlı değildir — her ikisi de soyutlamalara bağımlıdır
  • DIP ≠ DI: ilke vs uygulama modeli; DI araçtır, DIP hedeftir
  • Soyutlamalar tüketiciye aittir (domain katmanı), sağlayıcıya değil (veri katmanı)
  • Kompozisyon kökü — uygulamada somut bağımlılıkların birleştirildiği tek yer
  • Hilt, Koin, Dagger — kablolamayı otomatikleştiren ancak DIP'in mimari kararının yerini almayan DI araçları
  • Domain katmanı DIP'e göre inşa edilmiş, çerçevelerden, UI'dan ve altyapı kütüphanelerinden bağımsız kalır

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun