Ders 04 / 09
Görünürlük
Genel, korumalı ve özel yöntemler; dışarıdan çağrının reddi, send ile public_send farkı, self alıcısı, korumalı yöntemin aynı sınıftaki başka nesnede çalışması ve alt sınıfta görünürlüğü değiştirmek.
Yayın Güncelleme
Bu derste
- Özel bir yöntemin dışarıdan çağrılınca NoMethodError verdiğini, nesnenin içinden çağrılabildiğini ölçer
- send ile public_send arasındaki farkı özel yöntem üzerinde gösterir
- Korumalı ile özel yöntemin başka bir nesne üzerindeki davranışını ayırır
- Alt sınıfta bir yöntemin görünürlüğünü public ile değiştirmenin yazanı değil tanımın sahibini değiştirdiğini ölçer
İçindekiler
Önceki ders sonunda hesap.komisyon çağrısının dışarıdan reddedildiğini, oysa net yönteminin aynı nesnenin içinden onu çağırabildiğini bıraktı. Yöntem aynı, yazan aynı, ama bir çağrı kabul edilirken öbürü reddediliyor. Fark yöntemin kendisinde mi, çağrıyı yapanda mı?
Bu dersin yazanı programcıdır: ölçülen yöntemlerin çoğunu sınıflar kendi gövdesinde def ile yazar; yazılmayan erişimciler ise sınırlayıcı ölçümde ayrıca ele alınır. Ders, yazanın değiştirmediği ikinci bir özelliği ölçer: yöntemin görünürlüğünü. M11/K03’te yöntemlerin kime açık olduğunu paketler üzerinden gördük; burada aynı soru tek bir sınıfın içinde sorulur.
Özel Yöntem Dışarıdan Çağrılmaz
Ruby’de üç görünürlük vardır: genel, korumalı ve özel. Genel yöntem herkese açıktır. Özel yöntem ise yalnız nesnenin kendi içinden, açık bir alıcı yazılmadan çağrılabilir. private anahtar sözcüğü, kendinden sonra tanımlanan yöntemleri özel yapar.
# yazan.rb — bir yontemi kimin yazdigini siniflandiran yardimci
# Siniflandirma sirayla sorar: once alicinin zincirinde bir tanim var mi,
# sonra tanimi alicinin kendi sinifi mi tasiyor, sonra tanim nasil uretildi.
def yazan(alici, ad)
zincir = alici.singleton_class
unless zincir.method_defined?(ad) || zincir.private_method_defined?(ad)
return ["kimse", "tanim yok"]
end
tanim = zincir.instance_method(ad)
sahip = tanim.owner
yer = sahip == zincir ? "tekil sinif" : sahip.to_s
return ["ata", yer] unless sahip == alici.class || sahip == zincir
iz = RubyVM::InstructionSequence.of(tanim)
return ["kod", "#{yer}, ruby govdesi yok"] if iz.nil?
return ["kod", "#{yer}, blok govdesi"] if iz.label.start_with?("block")
["programci", "#{yer}, def govdesi"]
end
# ozel.rb — ozel yontem disaridan reddedilir
require_relative "yazan"
class Hesap
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
def net = @bakiye - komisyon
private
def komisyon = 2
end
hesap = Hesap.new("ece", 100)
p hesap.net
p yazan(hesap, :komisyon)
begin
hesap.komisyon
rescue NoMethodError => e
puts e.class
end
p hesap.send(:komisyon)
begin
hesap.public_send(:komisyon)
rescue NoMethodError => e
puts e.class
end
p Hesap.private_method_defined?(:komisyon)
p Hesap.public_method_defined?(:komisyon)
98 ["programci", "Hesap, def govdesi"] NoMethodError 2 NoMethodError true false
hesap.net 98 verdi: içeriden çağrı komisyon’u buldu. yazan programcı yazanını verdi, yani özel olmak yazanı değiştirmedi. Dışarıdan hesap.komisyon ise NoMethodError verdi. Aynı yöntemi send ile çağırmak çalıştı ve 2 verdi, public_send ise reddedildi. send görünürlüğü yok sayan, public_send ise onu uygulayan çağrıdır. Son iki satır görünürlüğü yöntem tanımı düzeyinde sorar: komisyon özel olarak kayıtlıdır, genel olarak değil.
SM9. Bu derste “görünürlük”, bir yöntemin kimin tarafından çağrılabileceğini belirleyen üç değerli özelliktir: genel, korumalı, özel. Görünürlük yazanı değiştirmez; yazan tanımın nerede olduğunu söyler, görünürlük çağrının kabul edilip edilmediğini.
Kendi Alıcısı ve Özel Yöntem
Özel yöntemin kuralı bir alıcı kuralıdır: açık alıcıyla çağrı reddedilir. Açık alıcı self ise bu kural esner. Aşağıdaki dosya aynı yöntemi üç biçimde çağırır.
# kendi_alicisi.rb — self alicisi ozel yontemi cagirabilir
class Hesap
def initialize(bakiye)
@bakiye = bakiye
end
def acik = komisyon
def kendi = self.komisyon
def baska(diger) = diger.komisyon
private
def komisyon = 2
end
a = Hesap.new(100)
b = Hesap.new(50)
p a.acik
p a.kendi
begin
a.baska(b)
rescue NoMethodError => e
puts e.class
end
2 2 NoMethodError
Alıcısız çağrı ve self.komisyon çalıştı, başka bir Hesap nesnesi üzerinden yapılan çağrı reddedildi. Özel yöntem bir sınıfa değil, bir nesneye özeldir: aynı sınıftan olsa bile başka bir nesne onun özel yöntemine ulaşamaz.
Korumalı Yöntem
İki hesabı karşılaştıran bir yöntem, karşı tarafın bakiyesine ihtiyaç duyar ama bakiyeyi herkese açmak istemeyebilir. Korumalı yöntem tam olarak bu iş içindir: dışarıdan çağrılamaz, ama aynı sınıftan başka bir nesne çağırabilir.
# korumali.rb — korumali yontem ayni siniftan nesneye acik
class Kumbara
def initialize(tutar)
@tutar = tutar
end
def fazla_mi?(diger) = tutar > diger.tutar
def gizli_fazla_mi?(diger) = gizli > diger.gizli
protected
def tutar = @tutar
private
def gizli = @tutar
end
a = Kumbara.new(10)
b = Kumbara.new(7)
p a.fazla_mi?(b)
begin
a.tutar
rescue NoMethodError => e
puts e.class
end
begin
a.gizli_fazla_mi?(b)
rescue NoMethodError => e
puts e.class
end
p Kumbara.protected_method_defined?(:tutar)
true NoMethodError NoMethodError true
fazla_mi? içinde diger.tutar çağrısı çalıştı: korumalı yöntem, çağıran nesne aynı sınıftan olduğu sürece başka bir nesnede kabul edilir. Dışarıdan a.tutar reddedildi. Benzer yapıdaki gizli_fazla_mi? ise diger.gizli satırında NoMethodError verdi, çünkü özel yöntem açık alıcıyı hiç kabul etmez. Korumalı yöntemin sınırı sınıftır, özel yöntemin sınırı nesne.
Korumalı yöntemin “aynı sınıftan” ölçütü tam olarak neyi kapsar? Aşağıdaki dosya çağıranı üst sınıf, alt sınıf ve ilgisiz bir sınıf olarak değiştirir.
# korumali_soy.rb — korumali yontemin siniri sahibin soyudur
class Kumbara
def initialize(tutar)
@tutar = tutar
end
def fazla_mi?(diger) = tutar > diger.tutar
protected
def tutar = @tutar
end
class Altin < Kumbara
end
class Yabanci
def sor(kumbara) = kumbara.tutar
end
p Kumbara.new(10).fazla_mi?(Altin.new(4))
p Altin.new(4).fazla_mi?(Kumbara.new(10))
begin
Yabanci.new.sor(Kumbara.new(1))
rescue NoMethodError => e
puts e.class
end
true false NoMethodError
Üst sınıf nesnesi alt sınıf nesnesinin korumalı yöntemini, alt sınıf nesnesi de üst sınıf nesnesininkini çağırabildi (sonuçlar true ve false: 10 > 4 ve 4 > 10): ölçüt çağıranın, yöntemin sahibi olan sınıfın soyundan olmasıdır. Yabanci bu soydan değil, bu yüzden aynı çağrı ona kapalı kaldı. Korumalı yöntemin sınırı bu yüzden dar bir sınıf adı eşleşmesi değil, kalıtım ağacıdır.
SM10. Bu derste “genel” herkese açık, “korumalı” aynı sınıftan nesnelere açık, “özel” yalnız alıcısız ya da self alıcılı çağrıya açık yöntemdir. Dışarıdan açık alıcıyla yapılan çağrı genel dışında her durumda reddedilir.
private Bir Değer Döndürür
private yalnız bir bildirim değildir; bir yöntem tanımının önüne konunca onu da özel yapar. def ifadesi yöntemin adını bir sembol olarak döndürür ve private o sembolü alır.
# private_def.rb — private def tek yontemi ozel yapar class Erisim private def yalniz_bu = 1 def acik = 2 end erisim = Erisim.new p erisim.acik p Erisim.private_instance_methods(false) p Erisim.public_instance_methods(false) begin erisim.yalniz_bu rescue NoMethodError => e puts e.class end
2 [:yalniz_bu] [:acik] NoMethodError
private def yalniz_bu = 1 yalnız o yöntemi özel yaptı; sonraki acik genel kaldı. Böylece görünürlük, bir satır boyunca değil, bir yöntem boyunca da yazılabilir.
private’ın aldığı değeri doğrudan görmek için def ifadesinin dönüşünü ve private çağrısının kendi dönüşünü bastırmak yeter. Aşağıdaki dosya ikisini ölçer.
# private_deger.rb — def sembol dondurur, private aldigini dondurur class Erisim p(def ilk = 1) p(private def ikinci = 2) p(private(:ilk)) end
:ilk :ikinci :ilk
def ifadesi yöntemin adını sembol olarak döndürdü, private def ise aldığı sembolü aynen geri verdi. private(:ilk) de aldığı adı döndürdü. Bu yüzden private def yalniz_bu = 1 yazımı çalışır: iç ifade bir ad üretir, dış çağrı o adı alıp görünürlüğünü değiştirir. Aynı sözleşme, görünürlüğü sonradan değiştirmenin de bir private :ad çağrısı olduğunu gösterir; tanımın yeri değişmez, yalnız kaydı değişir.
Soru Soran Yöntemler ve Kalıtım
Görünürlük iki yerde daha karşımıza çıkar. Birincisi respond_to? sorusudur: bir nesnenin yanıt verip vermediği, çağrının dışarıdan kabul edilip edilmediğine göre değişir. İkincisi kalıtımdır: özel yöntem alt sınıfa geçer mi? Aşağıdaki dosya ikisini ölçer.
# soru_ve_kalitim.rb — respond_to? ve alt siniftan cagri
class Hesap
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
private
def komisyon = 2
end
class Vadeli < Hesap
def net_vadeli = @bakiye - komisyon * 2
def disardan(diger) = diger.komisyon
end
v = Vadeli.new("can", 50)
p v.respond_to?(:komisyon)
p v.respond_to?(:komisyon, true)
p v.net_vadeli
begin
v.disardan(Vadeli.new("a", 1))
rescue NoMethodError => e
puts e.class
end
p Vadeli.private_method_defined?(:komisyon)
false true 46 NoMethodError true
respond_to?(:komisyon) yanlış verdi, respond_to?(:komisyon, true) ise doğru: ikinci argüman özel yöntemleri de soruya katar. Vadeli kendi gövdesinde komisyon’u alıcısız çağırabildi ve net_vadeli 46 verdi; özel yöntem alt sınıfa geçer, çünkü kural çağrının biçimine bakar, tanımın sahibine değil. Ama diger.komisyon gibi açık alıcılı çağrı, alt sınıfın içinde bile olsa reddedildi. Vadeli.private_method_defined?(:komisyon) doğru: görünürlük de zincir boyunca taşınır.
Özel Yazıcı ve Özel Sınıf Yöntemi
Alıcı kuralının self ile esnemesi en çok yazıcı yöntemlerde işe yarar: deger = 5 yazımı yerel değişken tanımlar, yöntem çağırmaz; yazıcıyı çağırmak için self.deger = 5 gerekir. Aşağıdaki dosya özel bir yazıcıyı bu yoldan çağırır. İkinci bölüm sınıf yöntemlerinin görünürlüğünü ölçer.
# yazici_ve_sinif.rb — self yazicisi ve private_class_method
class Sayac
def initialize
@deger = 0
end
def artir
self.deger = deger + 1
deger
end
private
attr_accessor :deger
end
class Form
def self.kur = new
private_class_method :new
end
s = Sayac.new
p s.artir
p s.artir
begin
s.deger = 9
rescue NoMethodError => e
puts e.class
end
p Form.kur.class
begin
Form.new
rescue NoMethodError => e
puts e.class
end
1 2 NoMethodError Form NoMethodError
artir özel deger= yazıcısını self.deger = ... biçiminde çağırdı ve sayaç 1, sonra 2 oldu; dışarıdan s.deger = 9 ise reddedildi. Sınıf yöntemleri için aynı kural private_class_method ile uygulanır: Form.new reddedildi, ama Form.kur sınıfın kendi içinden new’ü alıcısız çağırdığı için nesneyi üretti. Böylece nesne üretiminin tek kapısı genel bir sınıf yöntemi olur.
Sınırlayıcı Ölçüm
Görünürlük yazanı değiştirmedi. Peki attr_reader gibi bir üretici özel bir erişimci doğurduğunda yazan kim olur? Ve bir alt sınıf üst sınıfın özel yöntemini genel yaparsa, yöntem kimin olur?
# sinirlayici.rb — private attr_reader ve alt sinifta public
require_relative "yazan"
class Kumbara
def initialize(tutar)
@tutar = tutar
end
private attr_reader :tutar
end
class Hesap
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
private
def komisyon = 2
end
class Hesap3 < Hesap
public :komisyon
end
p yazan(Kumbara.new(3), :tutar)
h = Hesap3.new("ece", 100)
p h.komisyon
p h.method(:komisyon).owner
p yazan(h, :komisyon)
p Hesap.new("a", 1).respond_to?(:komisyon)
["kod", "Kumbara, ruby govdesi yok"] 2 Hesap3 ["programci", "Hesap3, def govdesi"] false
private attr_reader :tutar ile üretilen erişimci özel olsa da yazan kod yazanını verdi: tanım def ile değil attr_reader çağrısıyla üretildi ve Ruby’de gövdesi yok. Ruby 4.0.7’de Hesap3’teki public :komisyon ise iki sonucu değiştirdi: h.komisyon artık 2 verdi ve yöntemin sahibi Hesap3 çıktı, Hesap değil. Bu sonuç, görünürlük çağrısının ne yaptığına bakılınca anlaşılır: üst sınıfın tanımı olduğu gibi kalır, public :komisyon ise alt sınıfa aynı adlı bir kayıt koyar. yazan zincirde ilk bulunan kaydı sayar, o kayıt Hesap3’te durur, bu yüzden programcı ve Hesap3, def govdesi çıktı. Üst sınıfın kendi nesnesi respond_to? sorusuna yanlış demeye devam etti: görünürlüğü değiştirmek yalnız alt sınıfı etkiler. Bu açıklamanın doğruluğunu aşağıdaki ölçüm sınar.
# sinirlayici_neden.rb — public :ad alt sinifa kayit koyar
class Hesap
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
private
def komisyon = 2
end
class Hesap3 < Hesap
public :komisyon
end
h = Hesap3.new("ece", 100)
p Hesap3.instance_methods(false)
p Hesap.instance_method(:komisyon).owner
p Hesap3.instance_method(:komisyon).owner
p h.method(:komisyon).super_method.nil?
p Hesap.private_instance_methods(false)
p Hesap.public_method_defined?(:komisyon)
[:komisyon] Hesap Hesap3 true [:komisyon, :initialize] false
Hesap3.instance_methods(false) komisyon’u listeledi: alt sınıfa gerçekten bir kayıt eklendi, oysa gövdesinde tek satır tanım yok. Hesap’ın tanımı Hesap sahipli kaldı, Hesap.private_instance_methods(false) onu hâlâ özel olarak sıraladı (listedeki initialize her sınıfın özel yöntemidir) ve Hesap.public_method_defined? yanlış verdi. Alt sınıftaki kaydın super_method’u ise nil çıktı: Ruby 4.0.7 bu kaydı ayrı bir gövde olarak değil, üst sınıfın tanımına bakan bir görünürlük kaydı olarak tutar ve zincirde ikinci bir halka saymaz. Görünürlük, tanımın bir özelliği değil, tanımı zincirde bulan kaydın özelliğidir.
SM15. Bu derste “kayıt”, bir sınıfın yöntem tablosundaki ad ve görünürlük çiftidir; genel ve korumalı kayıtlar instance_methods(false), özel kayıtlar private_instance_methods(false) ile okunur. Görünürlük çağrısı kaydı alt sınıfa koyar; tanımın gövdesini kopyalamaz.
Hangi Görünürlük Ne Zaman
Üç görünürlüğün seçimi, bir yöntemin kime hizmet ettiğine bağlıdır. Sınıfın dışına verdiği sözleşme genel yöntemlerdir: onları çağıran kodlar var olabilir ve sınıf onları değiştirirse o kodlar kırılır. Hesap örneğinde net böyle bir sözleşmedir. komisyon ise net’in nasıl hesaplandığını anlatan bir ayrıntıdır; özel yapıldığı için sınıf onu istediği gibi değiştirebilir, çünkü dışarıdan çağıran kod yoktur.
Korumalı yöntem bu ikisinin arasındaki dar bir boşluğu doldurur: sınıfın nesneleri birbirleriyle konuşurken kullandığı, ama dışarıya açılmayan sözleşme. Kumbara örneğinde tutar böyledir. Korumalı yöntemin gerektiği yer, karşılaştırma gibi iki nesneyi bir arada ele alan yöntemlerdir: fazla_mi? öbür nesnenin tutar’ını ancak korumalı olduğu için okudu. Yöntemi yalnız nesnenin kendisi çağırıyorsa özel yapıp self alıcısıyla çağırmak yeter.
Bu derste görülen bir başka sonuç, görünürlüğün bir güvenlik önlemi olmadığıdır. send özel yöntemi çağırabildi; yani görünürlük bir sözleşmeyi belgeler ve yanlışlıkla çağrıyı engeller, kasıtlı bir çağrıyı durdurmaz. Bu yüzden özel bir yöntemi send ile çağıran kod, sınıfın izin vermediği bir şeyi bilerek yapar ve sınıf o yöntemi değiştirdiğinde ilk kırılan o olur.
Özet
- Özel yöntem dışarıdan açık alıcıyla çağrılamaz ve
NoMethodErrorverir; nesnenin içinden alıcısız ya daselfalıcısıyla çağrılabilir. sendgörünürlüğü yok sayar ve 2 verir;public_senduygular ve reddeder.- Korumalı yöntem aynı sınıftan başka nesnede çağrılabilir; özel yöntem başka bir nesnede çağrılamaz.
private def x = 1yalnız o yöntemi özel yapar; görünürlükprivate_instance_methods(false)ile sorulur.- Bu dersin yazanı programcıdır:
komisyoniçinyazanprogramcı,private attr_reader :tutariçin kod verdi. - Alt sınıfta
public :komisyongörünürlüğü yalnız o sınıfta değiştirir; alt sınıfa bir kayıt koyar,Hesap’ın özel tanımı yerinde kalır ve sahipHesap3çıkar.
Sonraki Adım
Kasa sınıfına bir Kilit modülü karıştırılmış ve her ikisi de ac tanımlıyor. Kasa.new.ac çağrısında hangi tanım önce çalışıyor, ve super zinciri bu sırayı nasıl izliyor? Sonraki ders, Yöntem Arama Zinciri, bu soruyu ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.