Bağımlılık (Coupling), bir uygulama modülünün başka bir modüle ne kadar bağımlı olduğunu gösteren bir metriktir. Wikipedia'ya göre, gevşek bağımlılık (low coupling), modüllerin komşularını bozmadan değiştirilebildiği iyi tasarlanmış bir sistemin işaretidir. Mobil uygulamalar tasarlarken bağımlılığı yönetmek, mimarın ana görevlerinden biridir.
Ana Noktalar
Coupling (bağımlılık), bir modülün veya sınıfın başka bir modüle ne kadar sıkı bağlı olduğunu belirleyen bir metriktir. Bir modül diğerinin iç yapısı hakkında ne kadar çok şey bilirse, coupling o kadar yüksek ve sistemi değiştirmek o kadar zor olur. İyi tasarlanmış bir mimaride coupling minimum düzeyde olmalıdır — modüller yalnızca kesin olarak tanımlanmış arayüzler aracılığıyla etkileşime girer.
Coupling'in iki yönü vardır: afferent (gelen bağımlılıklar — kaç modül buna bağımlı) ve efferent (giden bağımlılıklar — bu modül kaç modüle bağımlı). Bu metrikleri analiz etmek, bir modüldeki değişikliğin diğer birçok modülü etkileyeceği mimari sıcak noktaları belirlemeye yardımcı olur. IntelliJ Dependency Analyzer ve Xcode Graph gibi araçlar bu bağlantıları görselleştirir.
Sıfır coupling'in imkansız olduğunu anlamak önemlidir — modüllerin bir şekilde etkileşime girmesi gerekir, aksi takdirde bu bir sistem değil, izole programlar topluluğudur. Mimarın görevi, coupling'i yönetilebilir ve şeffaf hale getirmektir. İdeal: modüller yalnızca arayüzler aracılığıyla etkileşime girer ve yalnızca basit veri iletir, birbirlerinin iç yapısını bilmez. Buna gevşek bağımlılık (loose coupling) denir.
Altı tür coupling, en iyiden en kötüye doğru bir ölçek oluşturur. Bu ölçeği anlamak, mevcut kodu değerlendirmeye ve yeniden düzenleme yönünü seçmeye yardımcı olur. Çoğu mobil projede karışık coupling türleri bulunur ve mimarın görevi, güçlü türleri kademeli olarak zayıf türlerle değiştirmektir.
Data coupling (veri bağımlılığı) — modüller yöntem parametreleri aracılığıyla yalnızca basit veri alışverişi yapar. Modül A, Modül B'nin yöntemini çağırır, ilkel türler veya basit yapılar iletir ve bir sonuç alır. Modül A, B'nin dahili olarak nasıl uygulandığını bilmez. Bu en arzu edilen coupling türüdür: değişikliklerin etkisini en aza indirir.
Örnek: EmailValidator.isValid(email: String): Boolean. Tüketici sınıf bir dize iletir ve bir Boolean alır, doğrulayıcının içindeki normal ifadeler veya doğrulama kuralları hakkında hiçbir fikri olmaz. Doğrulama mantığını değiştirmek, tüketiciyi değiştirmeyi gerektirmez — coupling minimumdur. Data coupling, bir uygulamadaki tüm genel arayüzlerin hedefidir.
Stamp coupling (damga bağımlılığı) — modüller bileşik nesneleri değiş tokuş eder ancak alanlarının yalnızca bir kısmını kullanır. Modül A, calculateDiscount yöntemine bir User nesnesi iletir, bu yöntem yalnızca user.status'u kullanır. Sorun: User yapısı değişirse (zorunlu bir alan eklenirse), calculateDiscount modülü değişmez, ancak User nesnesini oluşturan tüketici değişir.
Uygulamada, damga bağımlılığı kaçınılmazdır ve iletilen nesne standart bir veri modeli (Entity) ise kabul edilebilir. Sorun, bir modülün yalnızca tek bir alan için bütün bir nesne alması durumunda ortaya çıkar. Bu gibi durumlarda, belirli değeri doğrudan iletmek daha iyidir (data coupling). Çözüm, alan kullanımını alıcı taraf açısından analiz etmektir.
Control coupling — bir modül diğerine davranışını kontrol eden bir bayrak iletir (calculate(useNewAlgorithm: Boolean)). Bu, stamp coupling'ten daha kötüdür çünkü tüketici modül, çağrılan modülün dahili çalışma varyantlarını bilmelidir. Çözüm: yöntemi ikiye bölmek — calculateWithNewAlgorithm() ve calculateWithLegacyAlgorithm().
External coupling — modüller harici bir protokole, veri formatına veya API'ye bağımlıdır. Aynı JSON'u ayrıştıran veya aynı veritabanıyla çalışan tüm modüller external coupling'e sahiptir. Tamamen önlenemez ancak izole edilebilir: harici format ile dahili modeller arasında bir eşleme katmanı oluşturun. Common coupling — modüller ortak bir global durumu paylaşır. Content coupling — en kötü tür, bir modülün başka bir modülün dahili verilerini doğrudan değiştirmesi.
| Coupling türü | Seviye | Açıklama |
|---|---|---|
| Data | En iyi | Parametrelerle basit veri iletimi |
| Stamp | Kabul edilebilir | Kısmi kullanımla nesne iletimi |
| Control | Orta | Bayraklarla davranış kontrolü |
| External | Yüksek | Harici protokol/forma bağımlılık |
| Common | Çok yüksek | Global durum paylaşımı |
| Content | Kabul edilemez | Modül iç verilerinin doğrudan değiştirilmesi |
Data (ideal) ile content (felaket) arasındaki coupling ölçeği, kod incelemeleri için pratik bir araçtır. Bir projede common veya content coupling görürseniz — bunlar öncelikli yeniden düzenleme hedefleridir. Data ve stamp coupling kabul edilebilir ve her projede bulunur, ancak miktarları kontrol edilmelidir.
Yüksek coupling, geliştirmeyi yavaş bir sürece dönüştürür ve her değişiklik düzinelerce potansiyel olarak bozuk modülün kontrol edilmesini gerektirir. Bu özellikle mobil geliştirmede kritiktir: platformlar yıllık (Android API Level, iOS SDK), kütüphaneler üç aylık ve iş gereksinimleri sürekli güncellenir. Gevşek bağımlılık, sürekli gerilemeler olmadan bu değişiklik akışıyla başa çıkmanın tek yoludur.
Pratik örnek: tüm ekranların doğrudan NetworkingManager ve DatabaseManager'ı içe aktardığı bir mobil uygulama. HTTP istemcisini Retrofit'ten Ktor'a (Android) veya URLSession'dan Alamofire'a (iOS) değiştirirken geliştirici her ekranı değiştirmek zorunda kalır. Düşük coupling ile, NetworkDataSource arayüzünün arkasında gizli bir uygulamayı değiştirmek yeterlidir — tüketiciler değişimi fark etmez.
Coupling'in birim test üzerindeki etkisi de çok büyüktür. Yüksek coupling'e sahip bir sınıf (doğrudan kurucuda bağımlılıklar oluşturma) izole olarak test edilemez — veritabanını, ağı ve kullanıcı arayüzünü de beraberinde sürükler. Böyle bir sınıfı test etmek için bir emülatör başlatmanız ve entegrasyon testlerini beklemeniz gerekir. Düşük coupling'e sahip bir sınıf, kurucu enjeksiyonu yoluyla bağımlılıkları kabul eder ve kolayca mock'lanabilir.
// Yüksek coupling — sınıf kendi bağımlılıklarını kendisi oluşturur
class ProfileViewModelHigh {
private val api = RetrofitApi()
private val db = RoomDatabase.getInstance()
private val cache = MemoryCache()
}
// Düşük coupling — bağımlılıklar kurucu aracılığıyla iletilir
class ProfileViewModelLow(
private val api: ApiService,
private val db: DatabaseService,
private val cache: CacheService
)
İlk durumda, ProfileViewModelHigh belirli uygulamalara sıkı sıkıya bağlıdır — Retrofit'i Ktor ile değiştirmek ViewModel kodunun değiştirilmesini gerektirir. İkinci durumda, ProfileViewModelLow yalnızca arayüzlere bağımlıdır ve uygulamaları dışarıdan sağlanır. İkinci sınıfı test etmek çok kolaydır: mock uygulamaları iletin ve emülatör olmadan mantığı doğrulayın.
Bağımlılığı Tersine Çevirme İlkesi (SOLID'de D), coupling'i azaltmanın temelidir. İlke, somut uygulamalara değil soyutlamalara bağımlı olmayı emreder. Bir sınıfın doğrudan bir RetrofitApi nesnesi oluşturması yerine, bir ApiService arayüzü alması gerekir. Bu, bağımlılığı belirli bir kütüphaneden soyutlama seviyesine kaydırır ve tüketiciyi değiştirmeden değiştirilebilir.
Observer deseni (veya reaktif versiyonları — StateFlow, Combine Publishers), veri kaynağı ve aboneler arasındaki coupling'i azaltır. Abone, verilerin nereden geldiğini bilmez — sadece değişikliklere tepki verir. Bu, göndericiyi ve alıcıyı birbirinden ayırır: mevcut aboneleri değiştirmeden yeni bir veri kaynağı eklenebilir. EventBus ve SharedFlow aynı prensipte çalışır.
Bridge deseni, soyutlamayı uygulamadan ayırarak bağımsız olarak değişmelerine olanak tanır. Mobil geliştirmede Bridge, örneğin platforma bağımlı modüller için kullanılır: iOS (Kingfisher, Nuke) ve Android (Glide, Coil) için farklı uygulamalara sahip ortak bir ImageLoader arayüzü. ImageLoader ile çalışan kod, seçilen kütüphaneye bağımlı değildir ve uygulamayı basitçe değiştirerek onu değiştirebilir.
Dependency Injection (DI), mobil geliştirmede coupling'i azaltmanın en pratik aracıdır. Bir sınıfın kendi bağımlılıklarını oluşturması yerine, bir DI kapsayıcısı (Android için Hilt, Koin, Dagger; iOS için Swinject, Factory) bunları dışarıdan sağlar. Sınıf, bağımlılıkları kurucu, yöntem veya özellik enjeksiyonu yoluyla alır, somut uygulamalar hakkında bilgi sahibi olmaz.
DI, sınıf bağımlılıklarını açıkça belgeler: sınıf hangi modüllerle etkileşime girdiğini anlamak için kurucuya bakmak yeterlidir. Kurucu farklı katmanlardan 8 parametre alıyorsa — bu aşırı coupling'in işaretidir ve yeniden düzenleme gerektirir. İyi uygulama, sınıf başına 3-4'ten fazla bağımlılık olmamasıdır. Daha fazlası, Tek Sorumluluk İlkesi ihlali ve aşırı coupling'i gösterir.
DI ayrıca test etmeyi de basitleştirir: her test için mock bağımlılıklarla bir sınıf oluşturursunuz, gerçek veritabanı veya ağ gerekmez. Flutter'da DI, Provider, Riverpod veya GetIt aracılığıyla uygulanır. Framework'ten bağımsız olarak hedef birdir: bağımlılıkları açık ve değiştirilebilir hale getirerek modüller arasındaki bağımlılığı azaltmak. Mobil projelerde DI kullanımı 2020'lerden bu yana fiili standarttır.
// DI kapsayıcısı bağımlılık grafiğini oluşturur
protocol AuthServiceProtocol {
func login(email: String, password: String) async throws -> User
}
final class AuthService: AuthServiceProtocol {
func login(email: String, password: String) async throws -> User {
// uygulama
}
}
// ViewModel belirli hizmeti bilmez — yalnızca protokol
final class LoginViewModel {
private let auth: AuthServiceProtocol
init(auth: AuthServiceProtocol) {
self.auth = auth
}
}
// DI Container, somut türlerin oluşturulduğu tek yerdir
final class DIContainer {
lazy var authService: AuthServiceProtocol = AuthService()
lazy var loginViewModel: LoginViewModel {
LoginViewModel(auth: self.authService)
}
}
Burada LoginViewModel belirli bir AuthService'e değil, yalnızca AuthServiceProtocol protokolüne bağımlıdır. Uygulamayı değiştirmek (örneğin, Firebase Auth'tan özel bir sunucuya geçiş) yalnızca DIContainer'da değişiklik gerektirir. AuthServiceProtocol'ün tüm tüketicileri etkilenmez — bağımlılık soyutlama ve DI aracılığıyla en aza indirilir.
Sıkça Sorulan Sorular
Cohesion bir modülün iç tutarlılığını ölçerken, coupling modüller arasındaki dış bağlantıyı ölçer. İyi bir mimari, yüksek cohesion ve düşük coupling hedefler. Bu metrikler ters orantılıdır: cohesion'ı artırmak genellikle coupling'i azaltır ve bunun tersi de geçerlidir.
Data ve stamp normaldir ve her projede bulunur. Control coupling sınırlı senaryolarda kabul edilebilir (örneğin, strategy deseni). External coupling, harici API'lerle çalışırken kaçınılmazdır ancak bir eşleme katmanının arkasında izole edilmelidir. Common ve content coupling, acil yeniden düzenleme gerektiren mimari sorunların işaretleridir.
Statik analiz araçları: IntelliJ IDEA Dependency Matrix, Xcode Graph, Gradle Bağımlılık raporu, SonarQube. Metrikler: afferent coupling (Ca), efferent coupling (Ce), Kararsızlık (Ce/(Ca+Ce)). Yüksek Kararsızlık (1'e yakın), modülün değiştirilmesinin kolay olduğu ve az sayıda şeyin ona referans verdiği anlamına gelir — bu iyidir.
Aşırı düşük coupling, kod gezinmesini zorlaştıran aşırı sayıda soyutlama ve arayüz anlamına gelebilir. Her sınıf için ayrı bir arayüz oluşturulursa, programcı dosyalar arasında geçiş yaparak zaman kaybeder. Denge: modülün harici API'si için arayüzler, ancak her dahili yardımcı sınıf için değil.
Strangler Fig tekniğini kullanın — doğrudan çağrıları kademeli olarak arayüzlerle değiştirin. En çok referans verilen sınıflar için arayüz çıkararak başlayın. Ardından bir DI kapsayıcısı ekleyin. İzole edilmiş kodu karakterizasyon testleriyle kapsayarak yeniden düzenlemenin sistem davranışını değiştirmediğinden emin olun.
Özet
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.
Ayrıca okuyun