Ders 01 / 19
Denetimin Ters Çevrilmesi
Bir nesne kabı, bir nesnenin kurulmasını, aynı örneğin geri dönmesini ve elle new ile kurulan bir örneğin hiçbir açıklamayı almamasını üç ayrı kaynaktan doğurur: on iki davranışın üçü kaynaktaki bir çağrıdan, altısı yalnız bir açıklamadan, üçü kabın kendi varsayılanından geliyor.
İçindekiler
Derleme, Test ve Ekosistem kursunun kapanış dersi bir sınıfın koşum sınıf yoluna nereden
girdiğini kaynak metnin söylemediğini ölçtü: çağıran taraf yalnız bir arayüz görüyordu,
arkasındaki sınıfın bir Java derleyicisinden geçip geçmediği hiç fark etmiyordu. Bu ders
aynı boşluğu bir adım öteye taşır. Orada eksik olan şey bir tipin nereden geldiğiydi;
burada eksik olan şey bir nesnenin kim tarafından kurulduğu. Bir uygulama çerçevesi
kullanan programda new anahtar sözcüğünü arayan biri çoğu zaman hiçbir şey bulmaz —
nesneler vardır, çalışırlar, ama onları kuran satır kaynakta yoktur.
Bu boşluğu dolduran düzenek denetimin ters çevrilmesidir (inversion of control): nesnenin ne zaman, hangi bağımlılıklarla ve kaç kez kurulacağına programın kendisi değil, programı çalıştıran bir nesne kabı karar verir. Normalde bir programın akışını yazdığı kod belirler — hangi sınıfın örnekleneceğine, hangi sırayla çağrılacağına yazan taraf karar verir. Nesne kabı bu kararı üstlendiğinde denetim yön değiştirir: artık hangi sınıfın ne zaman kurulacağına karar veren taraf, o sınıfı yazan değil, kabın kendisidir.
Bu ders o kabı modeller — gerçek bir çerçevenin adı, açıklama söz dizimi ya da davranışı
burada kullanılmıyor; kurulan şey yalnız standart kitaplığın yansıma (java.lang.reflect)
ve açıklama tipi (java.lang.annotation) araçlarıyla yazılmış küçük bir sınıf. Sorulan soru
kurs boyunca aynı kalır: bir davranışı kaynakta bir çağrı mı, bir açıklama mı
doğurdu, yoksa kaynakta hiçbir iz bırakmayan bir varsayılan mı?
Nesne Kabı Ne Modelliyor
Aşağıda kurulan Kap sınıfı dört açıklama tipi tanır: Bilesen, bir sınıfın kabın
yönetimine girdiğini işaretler ve isteğe bağlı bir kapsam taşır; Kur, bir yöntemin
nesne hazır olduktan sonra çağrılmasını ister; Sarmala, bir sınıfın çağrıları sayan
bir vekil arkasına konmasını ister; Ayar, bir alanın dışarıdan verilen bir değerle
doldurulmasını ister. Bu dördü de kabın kendi tanımıdır — bu dersin sözlüğüne
docs/TERMINOLOGY.md değil, bu kurs katkıda bulunur.
Kabın yönettiği nesne bu kursta kap bileşeni diye anılır. Kap bileşeni yalnız Bilesen
açıklamasını taşıyan bir sınıfın örneğidir; kap onu kurar, ihtiyaç duyduğu bağımlılıkları
yapıcı üzerinden çözer, Kur işaretli bir yöntemi varsa çağırır ve kapsam tekilse aynı
örneği saklar. Kap bileşeninin kurulumu şu adımları izler: yapıcıyı bul, parametrelerini
sırayla çöz (her parametre de bir kap bileşeni olabileceğinden bu adım kendini çağırır),
örneği oluştur, Ayar işaretli alanları doldur, Kur işaretli yöntemi çağır, Sarmala
işaretliyse bir vekil arkasına koy. Bu ders yalnız ilk iki adımı derinlemesine ölçer —
kurulum ve kapsam; Kur, Ayar ve Sarmala bu payın sırasıyla üçüncü, dördüncü ve altıncı
derslerinin konusudur ve burada yalnız modelin bir parçası olarak görünürler.
- CK1. Nesne kabı burada bir model olarak kuruluyor; hiçbir gerçek çerçevenin adı,
açıklama söz dizimi ya da API’si kullanılmıyor.
Bilesen,Kur,Sarmala,Ayarbu dersin kendi açıklama tipleri. - CK2. Kimlik yalnızca
==ile karşılaştırılıyor; hiçbir nesnenin kimlik karması basılmıyor. - CK3. Tekil kapsam kabın varsayılan kapsamı; aksi açıkça bir açıklamayla istenmedikçe her kap bileşeni tekildir.
- CK4. Yapıcı bağımlılıkları kap tarafından özyinelemeli çözülüyor; alan üzerinden bağımlılık verme bu payda kurulmuyor.
- CK5. Ölçüm gerçek bir ağ, veritabanı ya da dosya sistemi çağrısı içermiyor; depo bellek içinde tutulan bir alan.
Kaynakta Görünmeyen Kurulum
Aşağıdaki program iki bileşenli küçük bir sistem kurar: Depo bir arayüz, BellekDepo onu
gerçekleştiren tek sınıf, Servis de Depo’ya bağımlı ikinci bir kap bileşeni. Hiçbir yerde
new BellekDepo() ya da new Servis(...) yazılmıyor — kabın al yöntemi çağrıldığında bu
nesneler kendiliğinden ortaya çıkıyor. Programın ikinci yarısı aynı BellekDepo’yu elle
kurup kapla kurulanla karşılaştırıyor; üçüncü yarısı da bozulan bir açıklamanın nereye
düştüğünü kaydediyor.
// Kap.java — bir davranisi kaynakta bir cagri mi, bir aciklama mi, kabin varsayilani mi dogurdu
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
public class Kap {
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
@interface Bilesen { String kapsam() default "tekil"; }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface Kur {}
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
@interface Sarmala {}
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
@interface Ayar { String ad(); String varsayilan(); }
static int cagri = 0, aciklama = 0, varsayilan = 0;
static int hata = 0, sessiz = 0, etkisiz = 0;
static void gozle(String davranis, String kaynak) {
switch (kaynak) {
case "cagri" -> cagri++;
case "aciklama" -> aciklama++;
default -> varsayilan++;
}
System.out.printf("%-38s %s%n", davranis, kaynak);
}
static void kural(String ad, String sonuc) {
switch (sonuc) {
case "hata" -> hata++;
case "sessiz" -> sessiz++;
default -> etkisiz++;
}
System.out.printf("%-38s %s%n", ad, sonuc);
}
static void ozet() {
System.out.printf("davranis: %d cagri, %d aciklama, %d varsayilan%n",
cagri, aciklama, varsayilan);
System.out.printf("kusur : %d hata, %d sessiz, %d etkisiz%n", hata, sessiz, etkisiz);
}
final Map<Class<?>, Object> tekiller = new HashMap<>();
final Map<String, String> ayarlar = new LinkedHashMap<>();
final List<String> gunluk = new ArrayList<>();
int sarmaCagrisi = 0;
final Map<Class<?>, Class<?>> baglar = new HashMap<>();
Kap ayar(String ad, String deger) { ayarlar.put(ad, deger); return this; }
Kap bagla(Class<?> arayuz, Class<?> gerceklestirim) { baglar.put(arayuz, gerceklestirim); return this; }
Object al(Class<?> tip) throws Exception {
if (tip.isInterface()) {
Class<?> gerceklestirim = baglar.get(tip);
if (gerceklestirim == null)
throw new IllegalStateException("arayuze baglanmis gerceklestirim yok: " + tip.getSimpleName());
return al(gerceklestirim);
}
Bilesen b = tip.getAnnotation(Bilesen.class);
if (b == null) throw new IllegalStateException("bilesen aciklamasi yok: " + tip.getSimpleName());
boolean tekil = b.kapsam().equals("tekil");
if (tekil && tekiller.containsKey(tip)) return tekiller.get(tip);
Constructor<?> yapici = tip.getDeclaredConstructors()[0];
yapici.setAccessible(true);
Object[] arg = new Object[yapici.getParameterCount()];
for (int i = 0; i < arg.length; i++) arg[i] = al(yapici.getParameterTypes()[i]);
Object ornek = yapici.newInstance(arg);
for (Field f : tip.getDeclaredFields()) {
Ayar a = f.getAnnotation(Ayar.class);
if (a == null) continue;
f.setAccessible(true);
f.set(ornek, ayarlar.getOrDefault(a.ad(), a.varsayilan()));
}
for (Method m : tip.getDeclaredMethods()) {
if (m.isAnnotationPresent(Kur.class)) { m.setAccessible(true); m.invoke(ornek); gunluk.add("kur:" + tip.getSimpleName()); }
}
if (tip.isAnnotationPresent(Sarmala.class) && tip.getInterfaces().length == 1) {
Object hedef = ornek;
ornek = Proxy.newProxyInstance(tip.getClassLoader(), tip.getInterfaces(),
(p, m, a) -> { sarmaCagrisi++; return m.invoke(hedef, a); });
}
if (tekil) tekiller.put(tip, ornek);
return ornek;
}
String dene(Class<?> tip) {
try { al(tip); return "tamam"; }
catch (Exception e) { return e.getClass().getSimpleName(); }
}
public static void main(String[] args) throws Exception {
Kap kap = new Kap().bagla(Depo.class, BellekDepo.class).ayar("depo.baslangic", "dolu");
System.out.println("-- davranisi ne dogurdu --");
Servis servis = (Servis) kap.al(Servis.class);
Kap.gozle("nesnenin olusturulmasi", "aciklama");
Kap.gozle("bagimliligin saglanmasi", servis.depo != null ? "aciklama" : "cagri");
Kap.gozle("ayni ornegin donmesi", kap.al(Servis.class) == servis ? "varsayilan" : "cagri");
Kap.gozle("her istekte yeni ornek", kap.al(Istek.class) != kap.al(Istek.class) ? "aciklama" : "varsayilan");
Kap.gozle("kurulum yonteminin cagrilmasi", kap.gunluk.contains("kur:BellekDepo") ? "aciklama" : "cagri");
Kap bos = new Kap().bagla(Depo.class, BellekDepo.class);
Servis varsayilanli = (Servis) bos.al(Servis.class);
Kap.gozle("ayarin varsayilan degeri", varsayilanli.rapor().equals("hazir:bos") ? "varsayilan" : "cagri");
Kap.gozle("ayarin dista verilmesi", servis.rapor().equals("hazir:dolu") ? "aciklama" : "varsayilan");
int oncekiSarma = kap.sarmaCagrisi;
servis.rapor();
Kap.gozle("cagrinin sarmalanmasi", kap.sarmaCagrisi > oncekiSarma ? "aciklama" : "varsayilan");
BellekDepo elle = new BellekDepo();
Kap.gozle("elle kurulan nesnede sarmalama", Proxy.isProxyClass(elle.getClass()) ? "aciklama" : "varsayilan");
Kap.gozle("dogrudan yontem cagrisi", elle.oku().equals("kurulmadi") ? "cagri" : "aciklama");
Kap.gozle("elle new ile olusturma", "cagri");
Kap.gozle("bagimliligin elle verilmesi", new Servis(elle).rapor().equals("kurulmadi") ? "cagri" : "aciklama");
System.out.println("-- aciklama kusurlari nereye dusuyor --");
Kap.kural("aciklamasi olmayan tip istendi", kap.dene(Aciklamasiz.class).equals("tamam") ? "sessiz" : "hata");
Kap k2 = new Kap().bagla(Depo.class, BellekDepo.class).ayar("depo.baslangic", "dolu");
YanlisAyar ya = (YanlisAyar) k2.al(YanlisAyar.class);
Kap.kural("ayar adi yanlis yazildi", ya.deger.equals("dolu") ? "etkisiz" : "sessiz");
System.out.println("beklenen deger dolu, gorulen : " + ya.deger);
Object y1 = k2.al(YanlisKapsam.class), y2 = k2.al(YanlisKapsam.class);
Kap.kural("kapsam adi taninmiyor", y1 == y2 ? "etkisiz" : "sessiz");
Object arayuzsuz = k2.al(ArayuzsuzBilesen.class);
Kap.kural("sarmalama arayuzsuz sinifa konuldu", arayuzsuz instanceof Proxy ? "hata" : "etkisiz");
Kap.ozet();
}
}
interface Depo { String oku(); }
@Kap.Bilesen @Kap.Sarmala
class BellekDepo implements Depo {
@Kap.Ayar(ad = "depo.baslangic", varsayilan = "bos") String baslangic;
String durum = "kurulmadi";
@Kap.Kur void hazirla() { durum = "hazir:" + baslangic; }
public String oku() { return durum; }
}
@Kap.Bilesen
class Servis {
final Depo depo;
Servis(Depo depo) { this.depo = depo; }
String rapor() { return depo.oku(); }
}
@Kap.Bilesen(kapsam = "her-istekte")
class Istek { }
class Aciklamasiz { }
@Kap.Bilesen @Kap.Sarmala
class ArayuzsuzBilesen { String selam() { return "selam"; } }
@Kap.Bilesen(kapsam = "tanimsiz-kapsam")
class YanlisKapsam { }
@Kap.Bilesen
class YanlisAyar {
@Kap.Ayar(ad = "depo.baslangıc", varsayilan = "bos") String deger;
}
-- davranisi ne dogurdu -- nesnenin olusturulmasi aciklama bagimliligin saglanmasi aciklama ayni ornegin donmesi varsayilan her istekte yeni ornek aciklama kurulum yonteminin cagrilmasi aciklama ayarin varsayilan degeri varsayilan ayarin dista verilmesi aciklama cagrinin sarmalanmasi aciklama elle kurulan nesnede sarmalama varsayilan dogrudan yontem cagrisi cagri elle new ile olusturma cagri bagimliligin elle verilmesi cagri -- aciklama kusurlari nereye dusuyor -- aciklamasi olmayan tip istendi hata ayar adi yanlis yazildi sessiz beklenen deger dolu, gorulen : bos kapsam adi taninmiyor sessiz sarmalama arayuzsuz sinifa konuldu etkisiz davranis: 3 cagri, 6 aciklama, 3 varsayilan kusur : 1 hata, 2 sessiz, 1 etkisiz
Üç Kaynak, On İki Davranış
Çıktının ilk satırı kod tarafından hiç yazdırılmadı — Kap.gozle("nesnenin olusturulmasi", "aciklama") çağrısı sabit bir dizgi veriyor, çünkü servis değişkeni
kap.al(Servis.class) çağrısından zaten dönmüştü ve o çağrıyı doğuran şey kaynakta hiçbir
satır değil, Servis sınıfının üzerindeki @Kap.Bilesen açıklamasıydı. Bu payın açılış
iddiası tam burada: nesnenin kurulması bir eylem değil bir etki. Programı okuyan biri
Servis servis = (Servis) kap.al(Servis.class); satırını görüyor, ama bu satır Servis
sınıfının nasıl kurulacağını söylemiyor — yalnız kaptan bir örnek istiyor. Kurma kararının
kendisi Servis.class üzerindeki açıklamada duruyor.
İkinci satır — kap.al(Servis.class) == servis — aynı isteği iki kez yapıyor ve iki kez de
aynı nesneyi alıyor. Bu, kabın varsayılan kararı: Bilesen açıklaması bir kapsam
belirtmediğinde kap tekil kapsamı seçiyor, ve bu seçim kaynağın hiçbir yerinde yazmıyor.
kapsam = "tekil" değerini @interface Bilesen { String kapsam() default "tekil"; }
satırında görebiliyoruz, ama Servis sınıfının kendisi bu değeri hiç anmıyor — açıklama
çıplak @Kap.Bilesen, parametresiz. Varsayılanın nereden geldiğini bilmek için kabın kod
kaynağına bakmak gerekiyor; Servis.java’ya bakmak yetmiyor.
Programın son üçte biri aynı BellekDepo sınıfını elle kuruyor: BellekDepo elle = new BellekDepo();. Bu satır kaynakta gerçekten duran bir çağrı — ve kabın kurduğu örnekten
üç şekilde farklı bir nesne üretiyor. Çıktının son çağrı satırı da aynı köke bağlı:
new Servis(elle) da elle yazılmış bir bağımlılık aktarımı; kap hiç araya girmiyor.
Kaynakta duran üç cagri satırı — doğrudan yöntem çağrısı, elle new, elle bağımlılık
verme — hepsi aynı gözlemi doğruluyor: kabı devre dışı bırakan her satır, kaynakta gerçekten
görünen bir satır.
Kalan davranışlar bu payın sonraki derslerinin konusu ve burada yalnız işaret ediliyor: “bağımlılığın sağlanması” ve “bağımlılığın elle verilmesi” ikinci dersin (Bağımlılık Enjeksiyonu), “her istekte yeni örnek” ve “kurulum yönteminin çağrılması” üçüncü dersin (Bileşen Kapsamları ve Yaşam Döngüsü), “ayarın varsayılan değeri” ve “ayarın dışta verilmesi” dördüncü dersin (Yapılandırma Kaynakları), “çağrının sarmalanması” da altıncı dersin (Görünüş Odaklı Programlama) konusu. On iki davranışın üçü kaynaktaki bir çağrıdan, altısı bir açıklamadan, üçü kabın kendi varsayılanından geliyor — kaynağı yukarıdan aşağı okuyan biri bu davranışların dörtte üçünü göremiyor.
Kabın Dışında Kalan Nesne
Elle kurulan BellekDepo üç şeyi almadı: hazirla yaşam döngüsü kancası hiç çağrılmadı
(durum alanı hâlâ "kurulmadi"), baslangic alanına hiçbir Ayar değeri yerleşmedi
(alan null kaldı, oku() yöntemi onu hiç kullanmadığı için çıktıda görünmedi ama bu satır
onu görmüyor), ve nesne bir vekil arkasına konmadı — Proxy.isProxyClass(elle.getClass())
false döndü. Bu üçü de kabın varsayılanı, ama tuhaf bir varsayılan: kabın hiç
karışmadığı, sınıfın kendi çıplak hâlinde kalması. Açıklama sınıfın üzerinde durmaya devam
ediyor — BellekDepo hâlâ @Kap.Bilesen, hâlâ @Kap.Sarmala — ama onu okuyan taraf yok.
Bu payın kuralı şudur: bir açıklama bir özellik değil bir istektir; isteği yerine
getirecek bir kap olmadığında hiçbir şey olmaz, ne hata ne uyarı. elle nesnesi tamamen
geçerli bir Java nesnesi, hiçbir istisna fırlatmadan üretildi, hiçbir kusur bayrağı
taşımıyor — yalnız kabın vermesi gereken üç şeyi almadı. Bu, ders boyunca ölçülen
davranışlardan ayrı bir kusur değil; kabın sınırının kendisi. Nesne kabının verdiği her şey,
yalnız o kabın verdiği kadarıyla vardır.
Dersin ikinci kural bloğu farklı bir kusuru gösteriyor: Aciklamasiz sınıfı hiçbir
@Kap.Bilesen taşımıyor ve kap ondan bir örnek istendiğinde hemen bir istisna fırlatıyor —
bu kursun dört kusurundan tek gürültülü olanı. Kusur, kabın kurulduğu satırda değil,
kap.al(Aciklamasiz.class) çağrıldığı anda, yani ilk istekte düşüyor: new Kap()
satırının kendisi hiçbir denetim yapmıyor, hangi sınıfların açıklamalı olduğunu önceden
taramıyor. Diğer üç kusur — yanlış yazılmış ayar adı, tanınmayan kapsam adı, arayüzsüz
sınıfa konan sarmalama işareti — bu payın ilerleyen derslerinde derinlemesine ölçülüyor;
burada yalnız özet satırında görünüyorlar.
Özet
- Denetimin ters çevrilmesi, bir nesnenin ne zaman ve nasıl kurulacağı kararının yazan taraftan nesne kabına geçmesidir; kap bu kararı sınıf üzerindeki açıklamalardan okur.
- Aynı isteği iki kez yapmak aynı örneği verir — bu, kabın tekil kapsam varsayılanı, ve kaynağın hiçbir satırında yazmaz.
- Elle
newile kurulan bir nesne kaynakta gerçekten duran bir çağrıdan doğar ve kabın yönetimine hiç girmez. - Kabın dışında kalan bir nesne, üzerindeki açıklamalar durmaya devam etse bile, o açıklamaların hiçbirini almaz — açıklama bir istektir, onu okuyan taraf yoksa karşılıksız kalır.
- Açıklaması olmayan bir tip istendiğinde kap hemen ve adıyla bir istisna fırlatır; bu kusur kabın kurulduğu anda değil, ilk istek anında düşer.
- On iki ölçülen davranışın üçü kaynaktaki bir çağrıdan, altısı bir açıklamadan, üçü kabın kendi varsayılanından geliyor.
Sonraki Adım
Elle new Servis(elle) satırı, aslında kap içindeki en önemli mekanizmalardan birini elle
taklit ediyordu: bir bağımlılığı bir yapıcıya vermek. Kap bunu Servis’in tek yapıcı
parametresine bakarak, o parametrenin tipini isteyerek ve dönen nesneyi geçirerek yapıyor —
bu satırların hepsi kaynakta yok, ama sonuç aynı. Sıradaki ders bu mekanizmanın kendisini
ölçer: bağımlılığın sağlanması ile elle verilmesi arasındaki fark neden yalnız bir okunabilir
biçim tercihi değil, sınanabilir bir tasarım kararı.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.