İçeriğe geç
academia.sh

Ders 02 / 12

Nesne Modeli Sezgisi

Sayıların, nil'in ve true'nun da nesne olması; 5 + 3'te alıcının 5, yanıtlayanın Integer olması; sınıfların kendilerinin de nesne olduğu ve kendi iletilerini başka bir sınıfın yanıtladığı.

Yayın Güncelleme

Bu derste

  • Sayı, nil ve true gibi değerlerin de bir sınıfa ait nesneler olduğunu koşumla gösterir
  • Bir iletide alıcıyı yanıtlayandan ayırır ve 5 + 3 örneğinde yanıtlayanı sahip ölçümüyle bulur
  • Sınıfların da nesne olduğunu ve onlara gönderilen iletiyi başka bir sınıfın yanıtladığını ölçer
İçindekiler

Önceki ders her işlemin bir alıcıya gönderilen ileti olduğunu kurdu ve 5 + 3’ün +‘sının Integer sınıfında tanımlı olduğunu on iki gözlemin içinde gösterdi. Bu cümle bir şeyi varsayıyordu: 5 bir nesnedir ve bir sınıfa aittir. Bu ders o varsayımı ölçer. JavaScript Temelleri kursunda (M06/K01) ilkel tipler ölçülmüştü; Ruby’de aynı soru başka biçimde sorulur: bir değere ileti gönderilebiliyorsa, o iletiyi kim karşılıyor?

Nesne yönelimi M08’de Java ile kurulmuştu ve nesne ile sınıf kavramları orada tanımlandı. Burada yeni olan, Ruby’de sayıların, nil’in ve doğruluk değerlerinin de aynı modelin içinde olmasıdır. Ayrıca bu derste çekirdekten tek bir satır alınır: tam sayi toplami.

Her Değer Bir Nesnedir

Ruby’de ilkel diye ayrı bir değer sınıfı yoktur. Aşağıdaki dosya sekiz farklı değere aynı iletiyi gönderir: class. Her satırda değerin gösterimi ve sınıfı yan yana basılır.

# degerler.rb — her degerin bir sinifi vardir
degerler = [5, 3.5, "ad", :ad, nil, true, [1], 1..2]

degerler.each do |deger|
  printf("%-6s %s\n", deger.inspect, deger.class)
end
5      Integer
3.5    Float
"ad"   String
:ad    Symbol
nil    NilClass
true   TrueClass
[1]    Array
1..2   Range

Sayının, ondalık sayının, dizginin, sembolün, nil’in, doğruluk değerinin, dizinin ve aralığın hepsi bir sınıftan gelir. nil ve true bir sınıfın tek örneğidir: NilClass ve TrueClass. Bu, değerlere ileti gönderilebileceği anlamına gelir. Aşağıdaki dosya sayıya, true’ya ve nil’e birer ileti gönderir ve her yöntemin sahibini sorar.

# ilkel_iletiler.rb — sayi, true ve nil iletileri yanitlar
puts 5.times.class
puts 5.method(:times).owner
puts true.method(:&).owner
puts nil.method(:to_a).owner
p nil.to_a
p true & false
Enumerator
Integer
TrueClass
NilClass
[]
false

5.times bir yineleyici döndürür ve sahibi Integer’dır. true & false bir işleç gibi görünür ama TrueClass’ta tanımlı bir yöntemdir. nil.to_a boş diziyi verir ve sahibi NilClass’tır. Üç iletinin üçü de alıcının kendi sınıfında yanıt buldu.

DT5. Bu derste “nesne”, üzerine ileti gönderilebilen her değerdir. Bir değerin bellekte nasıl tutulduğu ölçülmez; yalnız davranış, yani hangi iletiye kimin yanıt verdiği ölçülür. object_id ve bellek adresi taşıyan inspect çıktıları bu yüzden basılmaz.

Alıcı ve Yanıtlayan

5 + 3 ifadesinde alıcı 5’tir, ileti +’dır, argüman 3‘tür. M11/K04’te işlemin hedef nesnesinin hangisi olduğu ölçülmüştü. Burada alıcı bellidir; soru, alıcıya gelen iletiyi hangi tanımın yanıtladığıdır. 5 + 3’te alıcı 5’tir, yanıtlayan Integer’dır.

# alici_yanitlayan.rb — 5 + 3'te alici ile yanitlayan ayri seylerdir
alici = 5

puts defined?(alici + 3)
puts alici.method(:+).owner
puts alici.method(:+).owner.instance_of?(Class)
puts alici + 3
method
Integer
true
8

defined? ifade için "method" döndürdü: ortada gerçekten bir ileti var. Yöntemin sahibi Integer’dır ve Integer bir Class örneğidir; kursun sınıflandırma kuralı bu durumda yanıtlayanı sınıf sayar. Alıcı bir değerdir, yanıtlayan bir tanımdır. İkisi çoğu zaman aynı adı taşımaz: 5 bir değerdir, Integer onun ait olduğu sınıftır.

Bu satır, kursun ortak çekirdeğindeki tam sayi toplami gözlemidir ve çekirdeğin tamamında olduğu gibi yanıtlayanı sınıftır. Bu derste yanit.rb çekilmez; defined? sonucu ve yöntemin sahibi doğrudan basılır. Kural aynıdır: önce defined?, sonra sahip.

Sınıflar da Nesnedir

Her değerin bir sınıfı olduğuna göre Integer‘ın da bir sınıfı olmalıdır. Integer Ruby’de bir sabittir ve bir nesneyi gösterir; o nesne de bir iletiyi yanıtlayabilir. Peki Integer’a bir ileti gönderildiğinde kim yanıtlar? Aşağıdaki dosya 5’in ve Integer’ın sınıfını, ardından Integer’a gönderilen === iletisinin sahibini basar.

# siniflar_nesne.rb — siniflar da nesnedir, kendi iletilerini baska bir sinif yanitlar
puts 5.class
puts Integer.class
puts Integer.method(:===).owner
puts Integer.method(:===).owner.instance_of?(Class)
puts Integer === 5
puts Integer === "5"
Integer
Class
Module
true
true
false

5’in sınıfı Integer’dır; Integer’ın sınıfı Class’tır. Yani Integer kendi başına bir nesnedir ve ona gönderilen === iletisini Integer değil Module yanıtlar. Module da bir sınıftır, bu yüzden kural yanıtlayanı yine sınıf sayar. Integer === 5 doğru, Integer === "5" yanlış döner: === burada “bu değer bu sınıfa ait mi” sorusunu sorar.

İleti Alıcının Sınıfına Bağlıdır

Her değerin bir sınıfı olması, her değerin her iletiyi yanıtlayamayacağı anlamına gelir. times iletisi Integer’da tanımlıdır; nil’in sınıfı NilClass’ta ise yoktur. Aşağıdaki dosya üç iletiyi iki alıcıya sorar ve nil’e times gönderildiğinde ne olduğuna bakar.

# ileti_yoklama.rb — ileti alicinin sinifina baglidir
[5, nil].each do |alici|
  printf("%-4s times: %-5s to_a: %-5s to_s: %s\n", alici.inspect, alici.respond_to?(:times), alici.respond_to?(:to_a), alici.respond_to?(:to_s))
end

begin
  nil.times
rescue NoMethodError => e
  puts e.class
end
5    times: true  to_a: false to_s: true
nil  times: false to_a: true  to_s: true
NoMethodError

respond_to? bir nesnenin bir iletiyi yanıtlayıp yanıtlamadığını sorar. 5 times ve to_s iletilerini yanıtlar; nil ise to_a ve to_s iletilerini yanıtlar, times iletisini yanıtlamaz. Yanıtlanmayan iletiyi göndermek NoMethodError fırlatır ve burada yalnız istisnanın sınıfı basıldı. Yani “her şey nesnedir” cümlesi “her nesne her iletiyi alır” anlamına gelmez: iletinin karşılığı alıcının sınıfında ya da o sınıfın üst tanımlarında bulunmalıdır.

Her Şeyin Nesne Olduğunu Yoklamak

Önceki bölümler nesne modelini alıcı, yanıtlayan ve sınıf üzerinden kurdu. Bu bölüm “her şey nesnedir” savını sayılar, nil ve sınıfların kendisi üzerinde tek tek sınar. Savın sınırı da ölçülür: bir değerin hangi türden olduğunu soran iki sorgu, instance_of? ve is_a?, aynı soruyu sormaz. Eksen yine yanıtlayandır; sorgulardan birinin sahibi çıktının son satırında okunur.

# her_sey_nesne.rb — nesne olmayan deger yoktur
puts 5.respond_to?(:+)
puts nil.respond_to?(:to_a)
puts 5.frozen?
puts 5.methods.size > 50
puts Integer.class
puts Class.class
puts 5.instance_of?(Integer)
puts 5.is_a?(Comparable)
puts 5.instance_of?(Comparable)
puts 5.method(:frozen?).owner
true
true
true
true
Class
Class
true
true
false
Kernel

İlk iki satır true verdi: 5 toplama iletisini karşılar, nil ise to_a iletisini karşılar. 5.frozen? de true döndü; sayı değişmez bir nesnedir. 5.methods.size > 50 sorusu true oldu. Burada sayının kendisini bilerek basmadık: bir nesnenin kaç yöntemi olduğu sürüme göre değişir, ancak “epeyce yöntemi olduğu” sonucu değişmez. Beşinci ve altıncı satırlar bölümün asıl bulgusudur: Integer.class ve Class.class ikisi de Class bastı. Integer bir sınıftır ve bir nesnedir; o nesnenin sınıfı Class’tır. Class’ın kendisi de bir nesnedir ve onun da sınıfı Class’tır. Zincir kendi üzerine kapanır, yani “sınıfın sınıfı nedir” sorusu sonsuza gitmez.

Son üç sorgu iki yöntemin farkını gösterir. 5.instance_of?(Integer) true, 5.is_a?(Comparable) true, 5.instance_of?(Comparable) ise false verdi. instance_of? yalnız nesnenin doğrudan sınıfını sorar; is_a? ise nesnenin o türden sayılıp sayılmadığını, yani daha geniş bir soruyu sorar. Zincirin nasıl işlediğini burada açmıyoruz; yalnız farkı kaydediyoruz. Son satırdaki sahip Kernel çıktı: 5.frozen? iletisini sayının sınıfı değil, her nesnenin ortak aldığı bir modül karşılar.

DT6. > 50 eşiği bir seçimdir: ölçüm yalnızca sayının eşikten büyük olduğunu gösterir ve sayının kendisi dilin sözü değildir.

# nil_nesne.rb — nil de bir nesnedir ve sahibi vardir
puts nil.class
puts nil.to_a.inspect
puts nil.to_s.empty?
puts nil.method(:to_a).owner
puts true.class
puts true.method(:&).owner
NilClass
[]
true
NilClass
TrueClass
TrueClass

nil de bir nesnedir: nil.class NilClass verdi. nil.to_a boş bir dizi döndürdü ve nil.to_s boş bir dizgi verdiği için empty? sorusu true oldu. Yani “değer yok” durumu bile iletilere yanıt verir ve bu yüzden nil üzerinde to_a çağırmak hata değil, [] üretir. nil.method(:to_a).owner NilClass çıktı: yanıtlayan nil’in kendi sınıfıdır. true için de aynı düzen geçerlidir: sınıfı TrueClass, & iletisinin sahibi de TrueClass. true ile nil‘in sınıfları ayrıdır ve ölçülen iki iletinin sahibi de bu iki sınıfın kendisi çıktı. Bu iki bölümün ortak sonucu, dersin başındaki sezgiyi sıkılaştırır: Ruby’de hiçbir değer iletiye kapalı değildir. Bir ifadenin değeri ne olursa olsun, sonuna bir ileti yazılabilir ve yanıtlayan bir sınıf ya da modül olarak bulunabilir.

Zincirleme İletiler ve Dönüş Nesnesi

Her ileti bir nesne döndürür ve döndürülen nesne bir sonraki iletinin alıcısı olur. Bu bölüm, zincirin her halkasında yanıtlayanın değişebildiğini ölçer. Ölçülen ifade 5.to_s.size.to_f gibi bir zincirdir; zinciri soldan okumak gerekir: önce 5 iletiyi alır, çıkan nesne bir sonrakini alır ve böylece sürer. Eksen yine sahiptir: iki halkanın sahibi ayrı ayrı basılır.

# zincir.rb — her ileti bir nesne dondurur ve zincir surer
sonuc = 5.to_s.size.to_f
puts sonuc
puts sonuc.class
puts 5.to_s.class
puts 5.to_s.size.class
puts [3, 1, 2].sort.reverse.first
puts "ruby".reverse.upcase.class
puts 5.method(:to_s).owner
puts 5.to_s.method(:size).owner
1.0
Float
String
Integer
3
String
Integer
String

Zincirin sonucu 1.0 oldu ve sınıfı Float’tır. Ara halkaları ayrı ayrı yoklayınca zincirin nasıl kurulduğu görünür: 5.to_s bir String verdi, 5.to_s.size ise bir Integer verdi. Yani aynı ifadenin içinde dört ayrı nesne ve üç ayrı sınıf vardır: 5 için Integer, "5" için String, 1 için yine Integer ve sonunda 1.0 için Float. [3, 1, 2].sort.reverse.first iki dizi halkasından sonra elemana indi ve 3 verdi; "ruby".reverse.upcase.class ise String bastı, çünkü iki halka da dizgi döndürür.

Sahip satırları, iki farklı halkanın iki farklı sınıfta tanımlı olduğunu gösterdi: 5.method(:to_s).owner Integer, 5.to_s.method(:size).owner ise String çıktı. Aynı to_s ve size adları başka bir alıcıda başka bir sahibe gidebilirdi; yanıtlayanı alıcının sınıfı belirler, zincirdeki sırası değil. Bu, ‘Ruby nesne yönelimlidir’ savının somut karşılığıdır: işlem bir nesneye bir ileti göndermektir ve cevap yine bir nesnedir. Bir zincirin okunması için her halkanın dönüş nesnesinin sınıfını bilmek gerekir; sınıf değiştiğinde bir sonraki ileti başka bir yanıtlayana gider. Bu yüzden uzun bir zincirde bir hata oluştuğunda, hatanın nerede çıktığını bulmanın yolu zinciri halka halka bölüp her ara nesnenin sınıfını yoklamaktır.

Eşitlik ve Kimlik Ayrı Sorulardır

Eşitlik ile kimlik iki ayrı sorudur. == iki nesnenin değerce eşit olup olmadığını, equal? ise ikisinin aynı nesne olup olmadığını sorar. Bu bölüm iki sorguyu aynı değerler üzerinde yan yana koyar. Eksen sahiptir: iki sorguyu karşılayan yöntemin tanımlandığı yer sonda basılır.

# kimlik.rb — esitlik ile kimlik ayri sorulardir
a = "x"
b = a
puts a.equal?(b)
puts a.equal?("x")
puts a == "x"
puts a.dup.equal?(a)
puts 1.equal?(1)
puts :s.equal?(:s)
puts nil.equal?(nil)
puts 1 == 1.0
puts 1.eql?(1.0)
puts 1.equal?(1.0)
puts a.method(:==).owner
puts a.method(:equal?).owner
true
false
true
false
true
true
true
true
false
false
String
BasicObject

b = a atamasından sonra a.equal?(b) true verdi: iki ad tek nesneyi gösteriyor. a.equal?("x") false, a == "x" ise true oldu: metin aynı, nesne farklı. a.dup.equal?(a) de false verdi, çünkü dup yeni bir nesne üretir. Sayı, sembol ve nil ise ayrı davrandı: 1.equal?(1), :s.equal?(:s) ve nil.equal?(nil) üçü de true verdi; bu değerlerin her yazımı aynı nesnedir.

Sayıların kendi içindeki karşılaştırmada üç sorgu iki farklı cevap verdi. 1 == 1.0 true oldu, 1.eql?(1.0) ile 1.equal?(1.0) ise false verdi. Yani == değerlere bakar ve tam sayı ile kesirli sayı arasında eşitlik görür; eql? ve equal? ise daha katı sorular sorar. Sahip satırları iki sorgunun tanımlandığı yeri gösterdi: == için String, equal? için BasicObject çıktı. Yani == her sınıfın kendi tanımına bırakılmış bir sorudur, equal? ise tek bir ortak yerde tanımlıdır. Bu yüzden bir değerin “aynı” olup olmadığını sormadan önce hangi anlamda sorulduğunu belirtmek gerekir. Sonuç olarak equal? kimlik sorusu için, == değer sorusu için kullanılır; ikisini karıştırmak, iki ayrı nesneyi aynı sanmanın ya da aynı nesneyi iki sanmanın yolunu açar.

Sınırlayıcı Ölçüm

Bu dersin ölçümlerinin çoğunda yanıtlayan sınıftır; class ve frozen? için Kernel modülü, equal? için BasicObject istisnadır. “Sınıf yanıtlayanı” ifadesi ise iki farklı şeye kayabilir. Kayma şöyle olur: Integer.=== iletisini yanıtlayan Integer zannedilebilir. Yukarıdaki koşum bunun yanlış olduğunu gösterdi. Yanıtlayan alıcının kendisi değil, alıcının ait olduğu sınıf zinciridir: alıcı 5 ise Integer, alıcı Integer ise Class zincirinde Module.

# alicinin_sinifi.rb — yanitlayan alicinin kendisi degil, ait oldugu siniftir
[5, "ad", Integer, String].each do |alici|
  printf("%-8s sinifi %-8s class sahibi %s\n", alici.inspect, alici.class, alici.method(:class).owner)
end
5        sinifi Integer  class sahibi Kernel
"ad"     sinifi String   class sahibi Kernel
Integer  sinifi Class    class sahibi Kernel
String   sinifi Class    class sahibi Kernel

class iletisinin sahibi dört alıcıda da Kernel’dir; çünkü bu ileti her nesnede ortak bir modülde tanımlıdır. Alıcıların sınıfları ise farklıdır. “Bu iletiyi kim yanıtlıyor” sorusunun iki katmanı vardır: alıcı hangi sınıfa ait ve o sınıf iletiyi kendi mi yanıtlıyor. Bu derste ölçülen birinci katmandır. İkincisinin, yani yöntemin alıcının sınıfında değil bir üst tanımda bulunduğu durumların ölçümü modül yanıtlayanını konu alan Yöntemler dersinde sürer.

Özet

  • Ruby’de sayılar, nil, true, dizgiler, semboller, diziler ve aralıklar birer nesnedir; her birinin bir sınıfı vardır.
  • 5 + 3’te alıcı 5’tir ve yanıtlayan Integer’dır; alıcı bir değerdir, yanıtlayan bir tanımdır.
  • 5.times, true & false ve nil.to_a iletilerini sırasıyla Integer, TrueClass ve NilClass yanıtlar.
  • Sınıflar da nesnedir: Integer.class Class’tır ve Integer.=== iletisini Module yanıtlar.
  • Bu dersin yanıtlayanı çoğunlukla sınıftır: iletiyi alıcının kendisi değil, alıcının ait olduğu sınıf karşılar. class ve frozen? gibi her nesnede ortak iletilerin sahibi ise Kernel modülüdür.
  • Integer.class ve Class.class ikisi de Class verir; equal? kimliği, == değeri sorar ve zincirdeki her halkanın yanıtlayanı o halkanın alıcısının sınıfıdır.

Sonraki Adım

Nesnelerin bir sınıfı olduğu ve iletinin onlara gönderildiği görüldü. Peki bir ad bir nesneye nasıl bağlanıyor ve x = 5 yazıldığında kim çalışıyor? Atamanın bir ileti olup olmadığı, ad kuralları ve kapsam işaretleri sonraki dersin, Değişkenler ve Sabitler’in konusudur.

İ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