Ders 01 / 09
Sınıf Tanımı ve Örnek Değişkenler
Bir yöntemi kimin yazdığını dört yazanla sınıflandırma; örnek değişkenlerinin nesnede, yöntemlerin sınıfta durması; initialize'ın özel oluşu; attr_reader ile yazılmış yöntemin def ile yazılmışa göre farkı.
Yayın Güncelleme
Bu derste
- Bir yöntemin yazanını programcı, ata, kod ve kimse olarak sınıflandırır
- Örnek değişkenlerinin nesnede, yöntemlerin sınıfta durduğunu instance_variables ve instance_methods ile ölçer
- Aynı sınıfta def ile yazılmış yöntem ile attr_reader ile üretilmiş yöntemi iseq üzerinden ayırır
İçindekiler
Önceki kurs bir iletinin karşılığını nerede bulduğunu ölçtü: alıcının sınıfında, karıştırılmış bir modülde, verilen blokta ya da sözdiziminde (M13/K01). Kapanışında bir soru açık kaldı. hesap.bakiye_metni, nokta.x, ayar.renk ve vekil.sort aynı biçimde çağrılır, ama dördünün gövdesi dört ayrı yoldan gelir. Yöntemin belirli bir sahipte durduğu biliniyor; o tanımı kimin yazdığı bilinmiyor.
Bu kurs o soruyu ölçer. İlk ders bir sınıfın en küçük hâlini alır: örnek değişkenleri nerede durur, yöntemler nerede durur, ve bir yöntemin yazanı nasıl sınıflandırılır?
Dört Yazan ve Ölçüm Çekirdeği
Kursun her dersi aynı soruyu sorar: bu yöntemi kim yazdı? Yanıt dört kategoriden biridir. Programcı, gövdeyi alıcının kendi sınıfında def ile yazan kişidir. Ata, tanımı zincirde bir üst sınıfta ya da bir modülde bırakandır. Kod, tanımı çalışma zamanında bir çağrıyla üreten düzenektir: attr_reader, define_method, Struct. Kimse, zincirde hiç tanım bulunmayan ve iletinin method_missing ile yakalandığı durumdur. Çıktıda bu dört ad ASCII yazılır: programci, ata, kod, kimse.
SM1. Bu kursta “yazan”, bir yöntemin tanımının nereden geldiğini söyleyen kategoridir. Sınıflandırmayı etiket değil kural yapar: alıcının tekil sınıfında tanım yoksa kimse; tanımın sahibi alıcının sınıfı ya da tekil sınıfı değilse ata; tanımın iseq değeri nil ya da etiketi block ile başlıyorsa kod; hiçbiri değilse programcı. Kural tanımın biçimini okur, yazanın niyetini okumaz. Bu cümle kursun birkaç sınırlayıcı ölçümünün ortak yüzüdür.
Kural yazan.rb dosyasındadır. Kurs boyunca yeniden yazılmaz, her derste aynı biçimde yüklenir. Dosyadaki sıra önemlidir. Önce tanımın var olup olmadığı sorulur, çünkü tanımı olmayan bir yöntemin sahibi sorulamaz. Sonra sahip sorulur, çünkü sahibi alıcının sınıfı olmayan bir tanım zaten başka bir yerden gelmiştir. En son tanımın nasıl üretildiğine bakılır: yalnız alıcının kendi sınıfında duran tanımlar için gövdenin bir def mi yoksa bir çağrının ürünü mü olduğunu ayırmak gerekir.
# 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
yazan(alici, ad) iki değerli bir dizi döndürür: yazanın adı ve kanıt. Kanıt, tanımın sahibini ve gövdenin nasıl yazıldığını söyler. Dokuz gözlemi bir arada koşan ikinci dosya yazanlar.rb’dir. Her gözlem aynı biçimde çağrılan bir yöntemdir; ayrım yalnız çağrının arkasındadır.
# yazanlar.rb — dokuz gozlem: bu yontemi kim yazdi
require_relative "yazan"
module Raporlanir
def rapor = "rapor: #{bakiye_metni}"
end
class Hesap
include Raporlanir
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
def bakiye_metni = "#{@sahip} #{@bakiye}"
def net = @bakiye - komisyon
private
def komisyon = 2
end
class Vadeli < Hesap
end
module Kilit
def ac = "kilit acildi, #{super}"
end
class Kasa
prepend Kilit
def ac = "kasa acildi"
end
Nokta = Struct.new(:x, :y)
class Ayar
%i[renk boyut].each do |ad|
define_method(ad) { "#{ad} varsayilan" }
end
end
class Vekil
def initialize(hedef)
@hedef = hedef
end
def method_missing(ad, *argumanlar, &blok)
return super unless @hedef.respond_to?(ad)
@hedef.public_send(ad, *argumanlar, &blok)
end
def respond_to_missing?(ad, ozel = false) = @hedef.respond_to?(ad, ozel) || super
end
class Model
def self.alan(ad, varsayilan)
define_method(ad) { instance_variable_get("@#{ad}") || varsayilan }
end
end
class Urun < Model
alan :ad, "adsiz"
alan :fiyat, 0
end
hesap = Hesap.new("ece", 100)
gozlemler = {
"durum okuma" => [hesap, :bakiye_metni, -> { hesap.bakiye_metni }],
"ust sinif" => [Vadeli.new("can", 50), :bakiye_metni, -> { Vadeli.new("can", 50).bakiye_metni }],
"karisim" => [hesap, :rapor, -> { hesap.rapor }],
"ozel yardimci" => [hesap, :komisyon, -> { hesap.net }],
"one eklenen" => [Kasa.new, :ac, -> { Kasa.new.ac }],
"uye okuyucu" => [Nokta.new(3, 4), :x, -> { Nokta.new(3, 4).x }],
"uretilen" => [Ayar.new, :renk, -> { Ayar.new.renk }],
"vekil" => [Vekil.new([3, 1, 2]), :sort, -> { Vekil.new([3, 1, 2]).sort }],
"bildirim" => [Urun.new, :fiyat, -> { Urun.new.fiyat }],
}
adet = { "programci" => 0, "ata" => 0, "kod" => 0, "kimse" => 0 }
printf("%-15s%-11s%-27s%s\n", "gozlem", "yazan", "kanit", "sonuc")
gozlemler.each do |ad, (alici, ileti, cagri)|
tur, kanit = yazan(alici, ileti)
adet[tur] += 1
printf("%-15s%-11s%-27s%s\n", ad, tur, kanit, cagri.call.inspect)
end
puts
adet.each { |tur, n| printf("%-11s%d\n", tur, n) }
puts "toplam gozlem: #{adet.values.sum}"
gozlem yazan kanit sonuc durum okuma programci Hesap, def govdesi "ece 100" ust sinif ata Hesap "can 50" karisim ata Raporlanir "rapor: ece 100" ozel yardimci programci Hesap, def govdesi 98 one eklenen ata Kilit "kilit acildi, kasa acildi" uye okuyucu kod Nokta, ruby govdesi yok 3 uretilen kod Ayar, blok govdesi "renk varsayilan" vekil kimse tanim yok [1, 2, 3] bildirim kod Urun, blok govdesi 0 programci 2 ata 3 kod 3 kimse 1 toplam gozlem: 9
Dokuz gözlemin 2’sini programcı, 3’ünü bir ata yazdı; 3’ünü bir kod üretti; 1’inde tanım yoktu, iletiyi method_missing yakaladı. Bu sayılar kurs boyunca değişmez. Programcı durum okuma ve ozel yardimci satırlarını yazdı: ikisinin gövdesi Hesap içinde bir def ile durur. Ata ust sinif, karisim ve one eklenen satırlarını yazdı; üçünün tanımı alıcıdan uzakta, Hesap’ta, Raporlanir’de ve Kilit’te durur. uye okuyucu, uretilen ve bildirim satırlarında tanımı bir çağrı üretti. vekil satırında tanım yok ve sonuç yine de döndü.
Bu dersin payı ilk programcı satırıdır: durum okuma. Sonraki dersler ata, kod ve kimse yazanlarını sırayla açar; bu ders ise programcının yazdığı en sade yapıyı, bir sınıfı ve onun örnek değişkenlerini ölçer.
Durum Nesnede, Davranış Sınıfta
Hesap sınıfının initialize yöntemi iki değer alır ve ikisini örnek değişkenine atar. Örnek değişkeni @ ile başlayan, nesneye bağlı bir değişkendir (M13/K01). Aşağıdaki dosya sınıfın küçük bir kesitini yeniden tanımlar, çünkü yazanlar.rb yüklendiğinde dokuz satırlık tabloyu basar; bu kesit onu tekrarlamadan çalışır.
# durum.rb — ornek degiskenleri nesnede durur
class Hesap
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
end
hesap = Hesap.new("ece", 100)
p hesap.instance_variables
p hesap.instance_variable_defined?(:@bakiye)
p Hesap.instance_methods(false)
begin
hesap.bakiye
rescue NoMethodError => e
puts e.class
end
[:@sahip, :@bakiye] true [] NoMethodError
instance_variables iki değişkeni atama sırasıyla listeledi: @sahip ve @bakiye. Değişkenler Hesap sınıfında değil, hesap nesnesinde durur. Hesap.instance_methods(false) ise boş bir dizi verdi: sınıfın genel bir yöntemi yok. initialize özeldir ve false argümanı yalnız sınıfın kendi tanımlarını sorar. Bu yüzden hesap.bakiye çağrısı NoMethodError verdi: değişken nesnenin içindedir, ama onu okuyan bir yöntem yazılmadığı için dışarıdan görünmez.
SM2. Bu derste “örnek değişkeni”, bir nesneye bağlı olan ve @ ile başlayan değişkendir. Değişkenin bir nesnede bulunması, o değişkeni okuyan bir yöntemin bulunduğu anlamına gelmez. Durum nesnede, davranış sınıfta durur; iki taraf @bakiye gibi bir adla birbirine bağlanır.
Bir örnek değişkeni, atanmadıkça nesnede hiç oluşmaz. Aşağıdaki dosya bunu ölçer: okunan değişken henüz atanmamıştır.
# atanmamis.rb — atanmamis ornek degiskeni nil okunur ve listede yoktur class Hesap2 def oku = @yok def yaz(deger) = @yok = deger end nesne = Hesap2.new p nesne.oku p nesne.instance_variables nesne.yaz(5) p nesne.oku p nesne.instance_variables
nil [] 5 [:@yok]
Atanmamış değişken nil olarak okundu, ama instance_variables listesine girmedi. Atama yapıldıktan sonra değişken hem okundu hem listelendi. Okumak nesnede bir şey oluşturmaz; yalnız atama oluşturur. Bu davranış Hesap2 gibi bir sınıfın nesneleri arasında bir fark da doğurur: yalnız yaz çağrılmış bir nesne @yok değişkenini taşır, çağrılmamış bir nesne taşımaz. İki nesne aynı sınıftan gelse de örnek değişkenlerinin listesi aynı olmak zorunda değildir; liste, sınıfın değil, nesnenin geçmişini yansıtır. Hata vermeyen bir okuma bu yüzden yazım hatasını gizleyebilir: @bakyie yazılırsa Ruby sessizce nil verir.
İki Nesne, Tek Sınıf
Durumun nesnede, davranışın sınıfta durması iki nesneyle ölçülür. Aşağıdaki dosya aynı sınıftan iki hesap üretir ve iki şeyi karşılaştırır: yöntemin sahibini ve örnek değişkenlerinin adlarını.
# iki.rb — iki nesne ayni yontemi paylasir, degerleri paylasmaz
class Hesap
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
def bakiye_metni = "#{@sahip} #{@bakiye}"
end
a = Hesap.new("ece", 100)
b = Hesap.new("can", 50)
puts a.bakiye_metni
puts b.bakiye_metni
p a.method(:bakiye_metni).owner == b.method(:bakiye_metni).owner
p a.instance_variables == b.instance_variables
p Hesap.instance_of?(Class)
p a.class
ece 100 can 50 true true true Hesap
Aynı yöntem iki nesnede iki ayrı sonuç verdi: ece 100 ve can 50. İki nesnenin yönteminin sahibi aynıdır, ikisinin örnek değişkenlerinin adları da aynıdır; ayrılan yalnız değerlerdir. Yöntem gövdesi bir kez, sınıfta yazılır ve her çağrıda çağrıyı alan nesnenin değişkenlerini okur. Hesap.instance_of?(Class) true verdi: class Hesap satırı Hesap adında bir sabit kurar ve sabitin gösterdiği şey Class türünde bir nesnedir. a.class bu nesneyi geri döndürür. Sınıf, yöntemlerin durduğu yer olduğu kadar, nesneleri üreten bir nesnedir de.
Bu ayrımın pratik bir sonucu vardır. Yöntem sayısı nesne sayısıyla çoğalmaz: bin hesap yaratıldığında bakiye_metni yine tek bir yerde, tek bir kez durur. Nesne başına çoğalan şey yalnız örnek değişkenlerinin değerleridir. Bir yöntemin yazanını sormak bu yüzden hep sınıfa ya da zincire yönelir; nesnenin kendisi yalnız durumu taşır ve yazanı soran ölçüm onun değişkenlerine bakmaz.
initialize Özeldir ve Argüman Sayısını Denetler
Hesap.new("ece", 100) çağrısı yeni bir nesne üretir ve initialize yöntemini bu nesnede çağırır. initialize’ı çağıran new’dir, dışarıdaki kod değil. Sınıf bunu özel yöntem olarak saklar; kod hesap.initialize(...) yazarak onu çağıramaz. Aşağıdaki dosya çekirdekteki Hesap’ın tam tanımını kullanır, çünkü özel yöntem komisyon da burada ölçülür.
# ozel.rb — initialize ozel bir yontemdir
require_relative "yazan"
class Hesap
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
def bakiye_metni = "#{@sahip} #{@bakiye}"
def net = @bakiye - komisyon
private
def komisyon = 2
end
hesap = Hesap.new("ece", 100)
p hesap.respond_to?(:initialize)
p Hesap.private_instance_methods(false).sort
p yazan(hesap, :initialize)
begin
Hesap.new("a")
rescue ArgumentError => e
puts e.class
end
false [:initialize, :komisyon] ["programci", "Hesap, def govdesi"] ArgumentError
respond_to?(:initialize) false verdi: özel yöntem dışarıdan sorulduğunda görünmez. private_instance_methods(false) sınıfın iki özel yöntemini listeledi: initialize ve komisyon. initialize için yazan programcıyı buldu, çünkü gövdeyi sınıfta bir def yazdı. Hesap.new("a") tek bir argümanla çağrıldığında ArgumentError verdi: initialize iki argüman bekler ve Ruby sayıyı çağrı anında denetler.
SM3. Bu derste “argüman sayısı”, bir yöntemin kabul ettiği argümanların sayısıdır; Hesap.new bu sayıyı initialize tanımından alır. initialize(sahip, bakiye) iki argüman ister; Hesap.new("a") bu yüzden ArgumentError verdi ve hiçbir nesne döndürmedi. Ruby sayıyı gövde koşmadan önce denetler, @sahip ataması da yapılmadı. Argüman sayısı böylece nesnenin ilk durumunu da sınırlar: iki örnek değişkenini kuran yapıcı, iki değer verilmeden çalışmaz. initialize içinde atanmayan bir değişken, sonradan başka bir yöntem tarafından atanmadıkça o nesnede hiç var olmaz; sınıfın yapıcısı bu yüzden nesnenin ilk durumunu kurduğu yerdir ve değişken adlarını bir arada görmek için en güvenilir noktadır.
Sınıfı yeniden açmak, aynı sınıf adıyla bir class bloğu daha yazmaktır. Mevcut tanım silinmez, yenisi eklenir (M12/K03). Bir sınıfın tanımı bu yüzden tek bir yerde olmak zorunda değildir; aşağıdaki ölçüm sınıfı yeniden açar.
Sınırlayıcı Ölçüm
Aynı sınıfta, aynı sahipte iki yazan bir arada bulunabilir. Hesap yeniden açılır ve attr_reader :sahip eklenir. attr_reader bir okuyucu yöntem üretir: sahip yöntemi @sahip’i döndürür. Önceki kurslarda (M08/K01) derleyicinin bir sınıfa ne eklediği ölçülmüştü. Burada soru farklıdır: sınıfta duran iki yöntemden birini bir def yazdı, ötekini bir çağrı üretti.
# yeniden.rb — ayni sinifta iki yazan: def ve attr_reader
require_relative "yazan"
class Hesap
def initialize(sahip, bakiye)
@sahip = sahip
@bakiye = bakiye
end
def bakiye_metni = "#{@sahip} #{@bakiye}"
def net = @bakiye - 2
end
class Hesap
attr_reader :sahip
end
hesap = Hesap.new("ece", 100)
p yazan(hesap, :sahip)
p yazan(hesap, :bakiye_metni)
p Hesap.instance_methods(false).sort
[:sahip, :bakiye_metni].each do |ad|
tanim = Hesap.instance_method(ad)
puts "#{ad} #{tanim.owner} #{tanim.source_location.nil?} #{RubyVM::InstructionSequence.of(tanim).nil?}"
end
["kod", "Hesap, ruby govdesi yok"] ["programci", "Hesap, def govdesi"] [:bakiye_metni, :net, :sahip] sahip Hesap false true bakiye_metni Hesap false false
yazan(hesap, :sahip) kodu, yazan(hesap, :bakiye_metni) programcıyı buldu. İkisinin sahibi de Hesap’tır ve ikisi de instance_methods(false) listesinde görünür: [:bakiye_metni, :net, :sahip]. Sahibi bilmek yazanı söylemez. Son döngü ayrımın nereden geldiğini gösterir. source_location iki yöntem için de nil değildir: attr_reader çağrısının satırı kaynak yeri olarak kayıtlıdır. Ayrımı iseq yapar. def ile yazılmış bakiye_metni için iseq vardır, attr_reader ile üretilmiş sahip için nil’dir. Kural bu yüzden kaynak yerine değil iseq’e bakar.
Bu ölçüm, sahibin yazanı belirlemediğini gösterir: iki yöntem aynı sahipte durdu ve iki ayrı yazanla çıktı. Aynı ölçüm sınıfın bir kimlik kartı olmadığını da gösterir. Hesap tek bir yazan taşımaz; her yöntemin yazanı ayrı ölçülür. Sınıf düzeyinde “bu sınıfı programcı yazdı” diyen bir kategori yoktur.
Özet
- Kursun ölçüsü, bir yöntemi kimin yazdığıdır: yazan programcı, ata, kod ya da kimse olabilir ve kararı
yazan.rbiçindeki kural verir. - Dokuz gözlemin 2’sini programcı, 3’ünü ata yazdı; 3’ünü kod üretti; 1’inde tanım yoktu ve
method_missingyakaladı. - Örnek değişkenleri nesnede, yöntemler sınıfta durur; okuyucu yoksa
hesap.bakiyeNoMethodErrorverir, atanmamış değişkennilokunur veinstance_variables’a girmez. initializeözel bir yöntemdir ve argüman sayısını denetler;Hesap.new("a")ArgumentErrorverir.- Bu dersin yazanı programcıdır:
durum okumasatırındabakiye_metni’niHesap’ın içinde birdefyazdı. - Aynı sınıfta
defile yazılmış yöntem programcı,attr_readerile üretilmiş yöntem kod çıkar; ikisinin sahibi aynıdır ve ayrımıiseqyapar.
Sonraki Adım
Hesap içinde bakiye_metni yazılı, ama Vadeli adlı boş bir alt sınıf da aynı yöntemi çağırıp yanıt alabiliyor. Alt sınıfta tek satır tanım yok. Yöntemi alt sınıfa kim getiriyor ve aynı yöntemi alt sınıfta yeniden yazmak neyi değiştiriyor? Sonraki ders, Kalıtım ve super, bunu ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.