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
alyö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.isProxyClassile 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
alyö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.