İçeriğe geç
academia.sh

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, Ayar bu 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 new ile 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat