İçeriğe geç
academia.sh

Ders 06 / 19

Görünüş Odaklı Programlama

Bir kap bileşenine konan bir sarmalama işareti, çağrı yerine hiç dokunmadan davranış ekliyor: kesişen bir ilgi (bu örnekte bir sayaç) her çağrıda araya giriyor ve düzenek bir vekildir. Ama bu işaretin var olması onun uygulanabilir olduğu anlamına gelmiyor — arayüzü olmayan bir sınıfa konan aynı işaret hiçbir hata vermeden etkisiz kalıyor.

İçindekiler

Önceki beş ders bir kap bileşeninin nasıl kurulduğunu, hangi bağımlılıkla donatıldığını, ne kadar yaşadığını, ayarlarını nereden aldığını ve hangi gerçekleştirimin otomatik seçildiğini ölçtü — hepsi bileşenin kendi iç hâliyle ilgiliydi. Önceki dersin Onbellek ve Metrik örnekleri çağrı yerine hiç dokunulmadan çalıştı, ama çağıran taraf yine de her zaman gerçek işi yapan nesneyle konuşuyordu. Bu ders farklı bir soru soruyor: bir bileşenin davranışına, onun kaynak kodunu hiç değiştirmeden, dışarıdan bir şey eklemek mümkün mü? Sunucu Temelleri kursu bu tür bir davranışı — birden çok sınıfa dağılmış, tek bir sınıfa ait olmayan bir ilgiyi — kesişen ilgi diye adlandırmıştı: günlükleme, sayaç tutma, yetki denetimi gibi, iş mantığının kendisiyle ilgisi olmayan ama ona eşlik eden davranışlar. Bu ders o kesişen ilgiyi, çağrı yerine hiç dokunmadan, bir kap bileşenine nasıl eklediğimizi ölçer.

Sarmalama: Çağrı Yerine Dokunmadan Araya Girmek

BellekDepo sınıfı @Kap.Sarmala taşıyor. Kap bir bileşeni kurarken bu açıklamayı görürse ve sınıf tam olarak bir arayüz gerçekleştiriyorsa, kurulan nesneyi doğrudan döndürmüyor — onun yerine bir vekil (proxy) döndürüyor. Vekil, Depo arayüzünü uygulayan ama gerçek işi yapmayan, her çağrıyı önce kendi üzerinden geçiren bir ara nesne: Proxy. newProxyInstance çağrısı, BellekDepo örneğini (hedef) saran ve her yöntem çağrısında sarmaCagrisi++ yapıp sonra çağrıyı gerçek nesneye ileten bir işleyici kuruyor. Servis sınıfının kendisi bu vekili hiç bilmiyor — depo.oku() çağırdığında elindeki referansın gerçek bir BellekDepo mu yoksa onu saran bir vekil mi olduğunu ayırt edemiyor, çünkü ikisi de aynı Depo arayüzünü taşıyor.

Bu, kesişen bir ilginin çağrı yerine hiç dokunmadan eklenmesinin somut hâli: Servis.rapor() yöntemi hâlâ depo.oku() çağırıyor, tek bir karakter bile değişmedi, ama artık her çağrı bir sayaç artışından geçiyor. Sayacı ekleyen şey ne Servis’in kaynak kodu ne BellekDepo’nun oku() gövdesi — ekleyen şey Sarmala işaretini gören kabın kendisi. Bu, önceki dersin otomatik yapılandırmasıyla aynı aileden bir düzenek: orada kap bir arayüz için hangi sınıfın kurulacağına karar veriyordu, burada kap kurulan nesnenin etrafına ne sarılacağına karar veriyor. İkisi de kaynakta iz bırakmayan bir kap kararı.

Proxy.newProxyInstance çağrısına verilen üçüncü bağımsız değişken bir işleyici — (p, m, a) -> { sarmaCagrisi++; return m.invoke(hedef, a); }. Bu üç harfli parametre kısaltması standart bir sözleşmeden geliyor: p çağrının yapıldığı vekil nesnenin kendisi, m çağrılan yöntemi temsil eden bir Method nesnesi, a da o çağrıya verilen bağımsız değişkenler dizisi. Vekile yapılan her çağrı — oku() olsun, ileride eklenecek başka bir yöntem olsun — bu tek işleyiciden geçiyor, çünkü Proxy.newProxyInstance çağrı başına ayrı bir yöntem üretmiyor, tek bir yakalayıcı kuruyor ve hangi yöntemin çağrıldığını m parametresiyle ayırt ediyor. Bu ölçümde Depo arayüzünün tek bir yöntemi var (oku()), bu yüzden m’nin değeri her zaman aynı; ama düzenek arayüze kaç yöntem eklenirse eklensin aynı kalıyor.

  • CK26. Vekil yalnız çağrı sayısını artırıyor, gerçek çağrıyı m.invoke(hedef, a) ile hedefe iletiyor; hiçbir çağrı vekil tarafından engellenmiyor ya da değiştirilmiyor.
  • CK27. Sarmalama yalnız tam olarak bir arayüz gerçekleştiren sınıflara uygulanıyor (tip.getInterfaces().length == 1); bu, Proxy.newProxyInstance’ın kendi kısıtı değil, bu ölçümün kendi basitleştirmesi. Birden çok arayüz gerçekleştiren bir sınıf bu ölçümde denenmiyor.
  • CK28. Sayaç yalnız çağrı sayısını tutuyor; hangi iş parçacığının çağırdığı, ne kadar sürdüğü ya da hangi bağımsız değişkenlerle çağrıldığı bu ölçümde yok.
  • CK29. Elle kurulan bir nesne kabın al yöntemine hiç girmiyor, bu yüzden vekil mantığına da hiç uğramıyor — sarmalama yalnız kabın kurduğu nesnelerde geçerli.
  • CK30. Kimlik yalnız Proxy.isProxyClass ile okunuyor; hiçbir vekil örneğinin kimlik karması basılmıyor.
// Kap.java — bir sarmalama isaretinin bir vekil araciligiyla davranis eklemesini kaynak hic degismeden nasil dogurdugu
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Kap {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Bilesen {}

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Sarmala {}

    static int aciklama = 0, varsayilan = 0;
    static int etkisiz = 0;

    static void gozle(String davranis, String kaynak) {
        if (kaynak.equals("varsayilan")) varsayilan++; else aciklama++;
        System.out.printf("%-38s %s%n", davranis, kaynak);
    }

    static void kural(String ad, String sonuc) {
        if (sonuc.equals("etkisiz")) etkisiz++;
        System.out.printf("%-38s %s%n", ad, sonuc);
    }

    static void ozet() {
        System.out.printf("davranis: %d aciklama, %d varsayilan%n", aciklama, varsayilan);
        System.out.printf("kusur   : %d etkisiz%n", etkisiz);
    }

    int sarmaCagrisi = 0;
    final Map<Class<?>, Class<?>> baglar = new HashMap<>();

    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());

        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);

        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); });
        }
        return ornek;
    }

    public static void main(String[] args) throws Exception {
        Kap kap = new Kap().bagla(Depo.class, BellekDepo.class);

        Servis servis = (Servis) kap.al(Servis.class);
        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");

        Object arayuzsuz = kap.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 {
    public String oku() { return "veri"; }
}

@Kap.Bilesen
class Servis {
    final Depo depo;
    Servis(Depo depo) { this.depo = depo; }
    String rapor() { return depo.oku(); }
}

@Kap.Bilesen @Kap.Sarmala
class ArayuzsuzBilesen { String selam() { return "selam"; } }
cagrinin sarmalanmasi                  aciklama
elle kurulan nesnede sarmalama         varsayilan
sarmalama arayuzsuz sinifa konuldu     etkisiz
davranis: 1 aciklama, 1 varsayilan
kusur   : 1 etkisiz

Bu payın birinci dersi aynı soruyu on iki davranış ve dört kusur üzerinden ölçmüş, 3 çağrı, 6 açıklama, 3 varsayılan ve 1 hata, 2 sessiz, 1 etkisiz toplamına varmıştı; bu ders o çekirdeğin yalnız sarmalama diliminde kalıyor.

Sayaç, Çağrı Yerine Hiç Dokunmadan Arttı

cagrinin sarmalanmasi satırı açıklama çıkıyor: oncekiSarma değişkeni servis.rapor() çağrılmadan hemen önceki sayaç değerini tutuyor, sonra servis.rapor() çağrılıyor, ve sonrasında kap.sarmaCagrisi > oncekiSarma doğru çıkıyor — sayaç gerçekten arttı. Bu satırın kendisinde depo.oku()’yu değil servis.rapor()’u çağırdığımıza dikkat etmek gerek: Servis.rapor() içeride depo.oku()’ya gidiyor, ve depo alanı aslında bir vekil olduğu için bu iç çağrı vekilin invoke işleyicisinden geçiyor. Servis’in kaynak kodunda vekili gösteren tek bir sözcük yok; Depo depo alanı yalnız arayüz tipini taşıyor, hangi somut sınıfın (gerçek BellekDepo mu, onu saran vekil mi) arkasında durduğunu söylemiyor.

sarmaCagrisi bir örnek alanı, Kap sınıfının kendisi değil — bu ölçüm tek bir kap nesnesi kullandığı için tek bir sayaç görüyoruz, ama iki ayrı Kap örneği aynı BellekDepo sınıfını ayrı ayrı sarmalasaydı, ikisinin sayaçları da birbirinden bağımsız kalırdı. Sayaç kabın kendi durumunun bir parçası, sarmalanan sınıfın değil — bu da Servis’in neden hiçbir zaman kendi sayacını tutmadığını, sayacın her zaman kapta durduğunu açıklıyor.

Elle kurulan BellekDepo bu sarmalamayı hiç almıyor: Proxy.isProxyClass(elle.getClass()) false dönüyor, bu yüzden elle kurulan nesnede sarmalama satırı varsayilan çıkıyor — kabın hiç karışmadığı bir durum. Birinci ders bunu önceden not etmişti; burada nedeni tam görünür oluyor: sarmalama yalnız al yönteminin içinde, nesne kurulduktan hemen sonra uygulanıyor. new BellekDepo() bu yöntemin dışında kaldığı için vekil hiç devreye girmiyor, ve elle kurulan nesne kalıcı olarak sarmalanmamış kalıyor — sonradan bir vekile “dönüştürülemiyor”, çünkü Java’da bir nesnenin kimliği kurulduktan sonra değişmiyor.

Sınırlayıcı Ölçüm: Görünen Ama Çalışmayan İşaret

ArayuzsuzBilesen sınıfı @Kap.Bilesen @Kap.Sarmala taşıyor — tıpkı BellekDepo gibi. Ama bir farkla: ArayuzsuzBilesen hiçbir arayüz gerçekleştirmiyor, yalnız kendi başına bir sınıf. al yöntemindeki sarmalama denetimi iki koşulu birden arıyor: tip. isAnnotationPresent(Sarmala.class) && tip.getInterfaces().length == 1. ArayuzsuzBilesen için birincisi doğru ama ikincisi yanlış — getInterfaces().length sıfır. Denetim false sonucuna varıyor ve sarmalama bloğuna hiç girilmiyor; nesne çıplak hâliyle döndürülüyor.

Object arayuzsuz = k2.al(ArayuzsuzBilesen.class);
Kap.kural("sarmalama arayuzsuz sinifa konuldu", arayuzsuz instanceof Proxy ? "hata" : "etkisiz");

arayuzsuz instanceof Proxy false çıkıyor, kural satırı "etkisiz" sınıfına düşüyor. Hiç istisna yok — Proxy.newProxyInstance hiç çağrılmadığı için fırlatacak bir hata da yok. Sonuç: ArayuzsuzBilesen kuruluyor, kullanılabiliyor, hiçbir şekilde bozuk görünmüyor — ama üzerindeki @Kap.Sarmala işareti tamamen etkisiz. Bu, ortak modelin kör noktası: bir sınıfın üzerinde duran bir açıklama, o açıklamanın gerçekten uygulandığı anlamına gelmiyor. Kaynağı okuyan biri @Kap.Sarmala‘yı görüp “bu sınıfın her çağrısı sayılıyor” diye düşünebilir, ama bu doğru değil — Proxy mekanizması yalnız arayüz üzerinden çalışabiliyor, çünkü Java’nın dinamik vekil düzeneği yalnız arayüzleri sarabiliyor, somut sınıfları değil.

Bu kusurun etkisiz sınıfında kalmasının nedeni tam bu teknik sınır: kap ArayuzsuzBilesen’i sarmalamayı reddetmiyor, yalnız sarmalayamıyor ve bunu hiç söylemiyor. Dört kusur ailesi içinde bu, en sessiz olanı — hata bağırıyor, sessiz yanlış bir değer üretiyor, ama etkisiz hiçbir iz bırakmıyor; program beklenen gibi çalışıyor görünüyor, yalnız beklenen tek bir davranış (sayaç artışı) hiç gerçekleşmiyor.

CK27’nin işaret ettiği gibi bu ölçüm yalnız sıfır arayüzlü uç durumu koşturuyor; simetriğini — birden çok arayüz gerçekleştiren bir sınıfı — hiç denemiyor. Ama denetimin kendi kod satırı (tip.getInterfaces().length == 1) bu durumda ne olacağını önceden söylüyor: iki arayüz gerçekleştiren bir sınıf da tıpkı ArayuzsuzBilesen gibi denetimden false alır ve sarmalanmadan döner — nedeni farklı (arayüz eksikliği değil, arayüz fazlalığı) ama sonuç aynı, sessiz bir etkisizlik. Bu, tek bir koşul satırının iki ayrı uç durumu aynı kusur sınıfına düşürmesinin bir örneği: Proxy.newProxyInstance’ın kendisi birden çok arayüzü aynı vekilin arkasında toplayabiliyor — üçüncü bağımsız değişkeni bir Class<?>[] dizisi kabul ediyor — ama bu ölçümün kendi denetimi bu yeteneği hiç kullanmıyor, yalnız tam bir arayüz durumunu doğru işliyor. Kaynağı okuyan biri @Kap.Sarmala’yı gördüğünde sınıfın kaç arayüz taşıdığına bakmadan “bu sınıf sarmalanacak” diye düşünebilir; oysa doğru yanıt yalnız getInterfaces().length’in tam olarak 1 olup olmadığına bağlı.

Altı Ders, Aynı Soru

Bu payın altı dersi hep aynı soruyu sordu — bu davranışı kaynakta bir çağrı mı, bir açıklama mı doğurdu, yoksa hiçbiri mi — ve her seferinde farklı bir köşeyi ölçtü. Birinci ders bu soruyu Kap.java’nın tam çekirdeğiyle yanıtladı: nesnenin kurulmasını ve tekil kalmasını, yapıcı üzerinden enjeksiyonu, kapsam ile kurulum yöntemini, ayar kaynaklarını ve sarmalamayı tek bir koşumda ölçüp on iki davranışla dört kusuru 3 çağrı, 6 açıklama, 3 varsayılan ile 1 hata, 2 sessiz, 1 etkisiz toplamına vardırdı. İkinci, üçüncü ve bu ders aynı çekirdeğin yalnız kendi sorusuna değen dilimini yeniden kurup koşturdu: yapıcı üzerinden enjeksiyonu ve bağlanmamış bir arayüzün hatasını, kapsam ile kurulum yöntemini ve tanınmayan bir kapsam adının sessizliğini, şimdi de sarmalamayı ve etkisiz kalan bir işareti — üçü de birinci dersin toplamının birer alt kümesi, kendi başlarına yeniden aynı on iki/dört toplamına varmayı hiç iddia etmiyor. Dördüncü ve beşinci ders bu çekirdeğin dışına çıkıp kendi ölçümlerini kurdu — üç yapılandırma kaynağının öncelik sırası, koşullu otomatik varsayılanlar — ama ikisi de aynı sözlüğü sürdürdü: bir davranışın kaynakta nereden doğduğunu sormaya, ve bozulduğunda gürültülü mü yoksa sessiz mi kaldığını ayırt etmeye devam etti.

Özet

  • Bir sarmalama açıklaması, çağrı yerine hiç dokunmadan, çağrı sırasında araya giren bir vekil kurarak davranış ekliyor; Servis’in kaynak kodu hiç değişmiyor.
  • Sarmalanan davranış (bu ölçümde bir sayaç) kaynakta hiçbir çağrı olarak görünmüyor — yalnız sayacın kendisi arttığında, koşturarak doğrulanabiliyor.
  • Elle kurulan bir nesne kabın al yönteminden hiç geçmediği için sarmalanmıyor ve kalıcı olarak sarmalanmamış kalıyor.
  • Arayüzü olmayan bir sınıfa konan sarmalama işareti hiçbir hata vermeden etkisiz kalıyor — bir açıklamanın var olması onun uygulanabilir olduğu anlamına gelmiyor.
  • Birinci derste ölçülen on iki davranışın üçü kaynaktaki bir çağrıdan, altısı bir açıklamadan, üçü kabın kendi varsayılanından geliyordu; bu derste eklenen sarmalama ve etkisiz kalan işaret, o toplamın üzerine oturan iki yeni gözlem.

Sonraki Adım

Bu payın altı dersi tek bir nesne kabının kendi iç işleyişini ölçtü: nesnenin kurulması, bağımlılığın sağlanması, kapsam ve yaşam döngüsü, ayarların kaynağı, koşullu varsayılanlar, ve şimdi çağrı yerine hiç dokunmadan eklenen davranış. Bu son ders bir sınır da çizdi: bir vekil yalnız arayüzün ulaşabildiği yere kadar davranış ekleyebiliyor, ötesine geçemiyor. Sıradaki konu aynı kabı, kendi iç işleyişinin dışında, bir isteği karşılarken izliyor — uç noktalar, doğrulama ve veri erişimi de tam bu payda ölçülen aynı üç kaynaktan doğuyor: bir çağrıdan, bir açıklamadan, ya da kabın kendi varsayılanından.

İ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