Ders 04 / 19
Bağımlılık Enjeksiyonu
Bir nesnenin ihtiyaç duyduğu bağımlılığı kendisi kurmak yerine dışarıdan alması ve sahte bağımlılığın neyi taklit ettiğinin denetimin ayrımını nasıl belirlediği.
Yayın Güncelleme
Bu derste
- Bağımlılığı içeride kurmakla dışarıdan vermenin denetime etkisini ayırt eder
- Gerçek ağ istemcisine bağlı bir denetimin neden iki sürümde de kaldığını ölçer
- Boş yanıt dönen bir güdükle kusuru yakalayan denetimi kurar
- Sahte bağımlılığın hangi yolu taklit ettiğinin denetimi ayıran ya da kör yaptığını gösterir
İçindekiler
Üçüncü derste durumu tek bir yere topladık ve indirgeyiciyi ekransız ölçtük. İndirgeyici dışarıdan hiçbir şey almadığı için ölçmek kolaydı. Çoğu nesne böyle değildir: bir ürün listesi görünüm modeli ürünleri bir ağ istemcisinden alır, bir sipariş nesnesi kaydı bir depodan okur. Bu bağımlılıklar nesnenin içinde kuruluyorsa nesneyi denetlemek, bağımlılığı da çalıştırmak demektir.
Bağımlılık enjeksiyonu bu bağı tersine çevirir: nesne ihtiyaç duyduğu bağımlılığı kendisi kurmaz, dışarıdan alır. Böylece denetimde gerçek bağımlılığın yerine ona benzeyen bir sahte bağımlılık verilebilir. Bu dersin kusuru ürün listesindedir: istemci boş bir yanıt aldığında önceki listeyi korumak yerine listeyi temizliyor. Çekirdek önceki derslerdeki gibi birebir gelir.
// Separation.swift — bir denetimin dogru surumu kusurlu surumden ayirip ayirmadigini siniflandirir
struct Contrast<Output: CustomStringConvertible> {
let label: String
let correct: Output
let faulty: Output
let passesCorrect: Bool
let passesFaulty: Bool
var separation: String {
switch (passesCorrect, passesFaulty) {
case (true, false): return "ayiran"
case (true, true): return "kor"
case (false, false): return "gurultulu"
case (false, true): return "ters"
}
}
func describe() -> String {
let onCorrect = passesCorrect ? "gecti" : "kaldi"
let onFaulty = passesFaulty ? "gecti" : "kaldi"
print("\(label) | dogru=\(correct)/\(onCorrect) kusurlu=\(faulty)/\(onFaulty) -> \(separation)")
return separation
}
}
func contrast<Output>(_ label: String, correct: Output, faulty: Output, check: (Output) -> Bool) -> Contrast<Output> {
Contrast(label: label, correct: correct, faulty: faulty, passesCorrect: check(correct), passesFaulty: check(faulty))
}
func countSeparations(_ results: [String]) {
for name in ["ayiran", "kor", "gurultulu", "ters"] {
print("\(name): \(results.filter { $0 == name }.count)")
}
}
Bu derste de çekirdek yalnız kullanılır. Ayrım adları aynıdır ve yalnız denetimlerin ne yaptığı değişir.
Bağımlılığı Dışarıdan Vermek
Enjeksiyonsuz görünüm modeli, istemcisini kendisi kurar. Bu sürümde denetim için iki yol vardır ve ikisi de kötüdür: gerçek ağa çıkmak ya da kurulan istemciyi değiştirmenin bir yolunu bulmak.
import Foundation
// Bagimlilik iceride kuruluyor — KAYNAK
final class ProductListViewModel {
private let service = NetworkProductService(session: URLSession.shared)
private(set) var items: [String] = []
func refresh() async {
guard let reply = await service.fetch() else { return }
items = reply
}
}
Bu parça çalıştırılmaz; oturum ve ağ platforma bağlıdır ve ders için çıktı iddia etmez. Yapısı yeter: service sabit olarak içeride kurulmuştur. Bu modelin refresh çağrısını denetlemek için ağın açık olması, sunucunun o anki yanıtı vermesi ve yanıtın denetimin beklediği olması gerekir. Üçü de denetimin elinde değildir.
Dışarıdan vermenin üç yolu vardır. En doğrudan olanı başlatıcı enjeksiyonudur: bağımlılık nesne kurulurken parametre olarak gelir. Özellik enjeksiyonunda nesne kurulduktan sonra atanır; işlev parametresi enjeksiyonunda yalnız o işlevin ihtiyacı olan çağrıda verilir. Bu derste başlatıcı kullanılır, çünkü bağımlılık nesnenin ömrü boyunca değişmez.
import Foundation
// Bagimlilik disaridan veriliyor — KAYNAK
protocol ProductService: Sendable {
func fetch() async -> [String]?
}
struct NetworkProductService: ProductService {
let session: URLSession
func fetch() async -> [String]? {
guard let url = URL(string: "https://example.com/products") else { return nil }
guard let (data, _) = try? await session.data(from: url) else { return nil }
return try? JSONDecoder().decode([String].self, from: data)
}
}
final class InjectedListViewModel {
private let service: any ProductService
private(set) var items: [String] = []
init(service: any ProductService) { self.service = service }
}
Bu parça da çalıştırılmaz. Önemli olan protokoldür: görünüm modeli artık gerçek istemciyi değil, ProductService sözleşmesini tanır. Gerçek istemci o sözleşmeye uyar, denetimdeki sahte bağımlılık da uyar. Görünüm modeli ikisini ayırt edemez.
Sahte Bağımlılık Neyi Taklit Eder
Model, protokolü ve iki sahte bağımlılığı saf Swift’le kurar. OfflineService ağın olmadığı durumu taklit eder: her çağrıda nil döner. StubService önceden verilen bir yanıtı döner; bu, çağrıldığında sabit yanıt veren bir güdüktür. Ürün listesi modeli refresh içinde üç yoldan birine girer: istek hata verirse liste korunur; yanıt boşsa doğru sürüm de listeyi korur, kusurlu sürüm listeyi temizler; yanıt doluysa liste yanıtla değişir. Kusur yalnız ikinci yoldadır. Catalog.swift bir yardımcı dosyadır ve çıktı vermez.
// Catalog.swift — urun listesi modeli ve sahte bagimliliklar
protocol ProductService: Sendable {
func fetch() async -> [String]?
}
struct OfflineService: ProductService {
func fetch() async -> [String]? { nil }
}
struct StubService: ProductService {
let reply: [String]
func fetch() async -> [String]? { reply }
}
struct ProductListModel {
private(set) var items: [String]
let service: any ProductService
let faulty: Bool
mutating func refresh() async {
guard let reply = await service.fetch() else { return }
if reply.isEmpty && !faulty { return }
items = reply
}
}
func refreshedItems(start: [String], service: any ProductService, faulty: Bool) async -> String {
var model = ProductListModel(items: start, service: service, faulty: faulty)
await model.refresh()
return model.items.isEmpty ? "bos" : model.items.joined(separator: ",")
}
Üç denetim aynı iki sürüme uygulanır. Birincisi gerçek ağ istemcisine bağlı denetimin yerini tutar. Sandbox’ta ağ kapalı olduğu için bu denetimde istemci OfflineService ile modellenir ve denetim sunucunun üç ürünlük listesini bekler. İkincisi boş yanıt dönen bir güdükle çalışır. Üçüncüsü ağ hatasını taklit eder ve listenin korunmasını bekler.
// main.swift — uc denetim, uc farkli sahte bagimlilik
let start = ["defter", "kalem"]
let goodReal = await refreshedItems(start: start, service: OfflineService(), faulty: false)
let badReal = await refreshedItems(start: start, service: OfflineService(), faulty: true)
let goodEmpty = await refreshedItems(start: start, service: StubService(reply: []), faulty: false)
let badEmpty = await refreshedItems(start: start, service: StubService(reply: []), faulty: true)
let real = contrast("gercek-ag-istemcisi", correct: goodReal, faulty: badReal) { $0 == "defter,kalem,silgi" }.describe()
let empty = contrast("bos-yanit-guduk", correct: goodEmpty, faulty: badEmpty) { $0 == "defter,kalem" }.describe()
let failure = contrast("hata-yaniti-guduk", correct: goodReal, faulty: badReal) { $0 == "defter,kalem" }.describe()
countSeparations([real, empty, failure])
gercek-ag-istemcisi | dogru=defter,kalem/kaldi kusurlu=defter,kalem/kaldi -> gurultulu bos-yanit-guduk | dogru=defter,kalem/gecti kusurlu=bos/kaldi -> ayiran hata-yaniti-guduk | dogru=defter,kalem/gecti kusurlu=defter,kalem/gecti -> kor ayiran: 1 kor: 1 gurultulu: 1 ters: 0
Çıktı Swift 6.4’te sandbox’ta üç kez koşturuldu ve üçünde de aynıdır. Üç denetim üç ayrı ayrımı verdi: bir ayıran, bir kör, bir gürültülü.
gercek-ag-istemcisi sunucudan gelecek üç ürünlük listeyi bekler. Sandbox’ta ağ olmadığı için istemci yanıt veremez, liste olduğu gibi kalır ve iki sürümde de defter,kalem çıkar. Denetim iki sürümde de kalır, yani gürültülüdür. Kusuru yakalamaz; kalması kusurdan değil, denetimin ağa bağlı olmasındandır. Aynı gürültü gerçek bir ortamda da vardır: sunucu yavaşsa, erişilemiyorsa ya da başka bir yanıt veriyorsa denetim kırmızı olur ve kırmızıya bakan kişi kusuru koda değil, ağa yorar.
bos-yanit-guduk listeyi koruyup korumadığına bakar ve güdük boş bir yanıt döner. Doğru sürüm defter,kalem verir ve geçer; kusurlu sürüm listeyi temizler, bos verir ve kalır. Denetim ayıran olur. Burada fark bağımlılığın dışarıdan verilmesidir: kusurun yaşadığı yol, yani boş yanıt, ancak bağımlılığı kontrol eden bir denetimle tetiklenir.
hata-yaniti-guduk ağ hatasını taklit eder ve listenin korunmasını bekler. Hata dalında iki sürüm de listeyi korur, bu yüzden denetim iki sürümde de geçer ve kördür. Kusur boş yanıt dalındadır, hata dalında değil. Sahte bağımlılık yanlış yolu taklit ettiğinde denetim doğru görünür ve kusuru kaçırır.
Varsayımlar
MM1. Gerçek ağ istemcisi ölçülmez. Sandbox’ta ağ kapalıdır; gerçek istemci yerine OfflineService ile “yanıt gelmiyor” durumu modellenir ve bu durumun denetimi gürültülüdür. Gerçek bir ortamda aynı denetimin nasıl davranacağı sunucuya bağlıdır ve bu ders ona bir değer vermez.
MM2. ProductService protokolünün modeldeki hâli gerçek istemcinin sözleşmesiyle aynıdır: bir yanıt listesi ya da nil. Gerçek istemcide hata türleri, kimlik doğrulama ve yeniden deneme vardır; model bunları tek bir nil’e indirger.
MM3. Yanıt bir ürün adları listesidir. Sayfalama, sıralama ve ürün alanları modele girmez.
Sınırlayıcı Ölçüm
Sahte bağımlılığın neyi taklit ettiği, ayrımı belirler. Güdük her çağrıda aynı dolu yanıtı dönerse kusurlu yol hiç koşmaz. Aynı iki sürüm iki güdükle denenir: biri hep üç ürünlük dolu yanıt döner, öteki boş yanıt döner.
// main.swift — ayni iki surum, iki farkli guduk
let start = ["defter", "kalem"]
let full = ["defter", "kalem", "silgi"]
let goodFull = await refreshedItems(start: start, service: StubService(reply: full), faulty: false)
let badFull = await refreshedItems(start: start, service: StubService(reply: full), faulty: true)
let goodNone = await refreshedItems(start: start, service: StubService(reply: []), faulty: false)
let badNone = await refreshedItems(start: start, service: StubService(reply: []), faulty: true)
let onFull = contrast("dolu-yanit-guduk", correct: goodFull, faulty: badFull) { $0 == "defter,kalem,silgi" }.describe()
let onNone = contrast("bos-yanit-guduk", correct: goodNone, faulty: badNone) { $0 == "defter,kalem" }.describe()
countSeparations([onFull, onNone])
dolu-yanit-guduk | dogru=defter,kalem,silgi/gecti kusurlu=defter,kalem,silgi/gecti -> kor bos-yanit-guduk | dogru=defter,kalem/gecti kusurlu=bos/kaldi -> ayiran ayiran: 1 kor: 1 gurultulu: 0 ters: 0
Üç koşumda çıktı aynıdır. Güdük hep dolu yanıt döndüğünde iki sürüm de listeyi yanıtla değiştirir ve denetim kördür; kusurun yaşadığı yol hiç çalışmaz. Güdük boş yanıt döndüğünde aynı denetim türü ayıran olur. Denetim değişmedi, sahte bağımlılığın taklit ettiği yanıt değişti. Bu, sahte bağımlılığın en sık yapılan hatasına işaret eder: güdüğü mutlu yol için kurmak. Mutlu yolda hiçbir şey kırılmaz, denetim yeşil kalır ve kusurlu yol bir sonraki gerçek boş yanıtta kullanıcıya ulaşır.
Özet
- Bağımlılık enjeksiyonu nesnenin ihtiyaç duyduğu bağımlılığı dışarıdan almasıdır; en yaygın yolu başlatıcıdan vermektir.
- Gerçek ağ istemcisine bağlı denetim ağa ve sunucuya bağlıdır; ağ yanıt vermeyince iki sürümde de kalır ve gürültülüdür.
- Boş yanıt dönen bir güdükle denetim, kusurun yaşadığı yolu tetikler ve ayıran olur.
- Hata yanıtını taklit eden güdük, kusur boş yanıt dalındaysa kördür: üç denetim bir ayıran, bir kör, bir gürültülü verdi.
- Güdük hep dolu yanıt dönerse kusurlu yol koşmaz ve denetim kör kalır.
- Bu dersin ayrımı: gürültülü.
Sonraki Adım
Bağımlılığı dışarıdan vermek, nesneleri birbirinden ayırdı; ama hepsi aynı hedefte duruyorsa her nesne her nesneyi görebilir. Beşinci derste Modülerleştirme, kodu paketlere böler ve hangi paketin hangisine bağlanabileceğini kurala bağlar. Orada kusur bir özellik modülünün başka bir özellik modülüne doğrudan bağlanması olacak, ve derlemenin geçip geçmediğine bakan denetim bu kusura karşı kör kalacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.