Object (Nesne)
Ruby, Object Oriented Programming'in dibidir :) Her şey nesnelerden oluşur. Nasıl mı? hemen basit bir değişken oluşturup içine bir tekst yazalım.
mesaj = "Merhaba"
mesaj
değişkeninin türü ne?
mesaj.class # => String
Bu değişken String nesnesinden türemiş. Peki, String nereden geliyor?
mesaj.class.superclass # => Object
Dikkat ettiyseniz burada superclass
kullandık. Yani bu hiyerarşideki bir üst
sınıfı arıyoruz. Karşımıza ne çıktı? Object. Peki acaba Object nereden
türemiş?
mesaj.class.superclass.superclass # => BasicObject
Hmmm.. Peki BasicObject nereden geliyor?
mesaj.class.superclass.superclass.superclass # => nil
İşte şu anda dibi bulduk :) Demek ki hiyerarşi;
BasicObject > Object > String
şeklinde bir hiyerarşi söz konusu.
Peki, sayılarda durum ne?
numara = 1
numara.class # => Fixnum
numara.class.superclass # => Integer
numara.class.superclass.superclass # => Numeric
numara.class.superclass.superclass.superclass # => Object
numara.class.superclass.superclass.superclass.superclass # => BasicObject
numara.class.superclass.superclass.superclass.superclass.superclass # => nil
Ufff... bir an için bitmeyecek sandım :) Çok basit bir sayı tanımlası yaptığımızda bile, arka plandaki işleyiş yukarıdaki gibi oluyor. Yani
BasicObject > Object > Numeric > Integer > Fixnum
şeklinde yine ana nesne BasicObject olmak koşuluyla uzun bir hiyerarşi söz konusu.
Her şey BasicObject den türüyor, bu yüzden de aslında her şey bir Class dolayısıyla bu durum dile çok ciddi esneklik kazandırıyor.
Nesne Metodları (Object Instance Methods)
Şimdi boş bir nesne oluşturalım. Class bölümünde daha detaylı göreceğimiz
instantiate işlemiyle new
methodunu kullanarak;
o = Object.new # => #<Object:0x007fe552099a68>
o.__id__ # => 70311450299700
yaptığımızda, oluşan nesnenin hafızada unique (yani bundan sadece bir
tane) bir identifier'ı (kabaca buna kimlik diyelim) yani ID'si
olduğunu görürüz. __id__
yerine object_id
yani o.object_id
şeklinde de
kullanabiliriz.
Eğer hash
method’unu çağırırsak, Ruby bize ilgili objenin Fixnum
türünde
sayısal değerini üretir ve verir.
o = Object.new # => #<Object:0x007f8c3b0a3420>
o.__id__ # => 70120131336720
o.object_id # => 70120131336720
o.hash # => -229260864779029724
Neticede String de bir nesne ve;
t = String.new("Hello") # => "Hello"
t.__id__ # => 70170408456140
t.methods # => [:<=>, :==, :===, :eql?, :hash, :casecmp, :+, :*, :%, :[], :[]=, :insert, :length, :size, :bytesize, :empty?, :=~, :match, :succ, :succ!, :next, :next!, :upto, :index, :rindex, :replace, :clear, :chr, :getbyte, :setbyte, :byteslice, :scrub, :scrub!, :freeze, :to_i, :to_f, :to_s, :to_str, :inspect, :dump, :upcase, :downcase, :capitalize, :swapcase, :upcase!, :downcase!, :capitalize!, :swapcase!, :hex, :oct, :split, :lines, :bytes, :chars, :codepoints, :reverse, :reverse!, :concat, :<<, :prepend, :crypt, :intern, :to_sym, :ord, :include?, :start_with?, :end_with?, :scan, :ljust, :rjust, :center, :sub, :gsub, :chop, :chomp, :strip, :lstrip, :rstrip, :sub!, :gsub!, :chop!, :chomp!, :strip!, :lstrip!, :rstrip!, :tr, :tr_s, :delete, :squeeze, :count, :tr!, :tr_s!, :delete!, :squeeze!, :each_line, :each_byte, :each_char, :each_codepoint, :sum, :slice, :slice!, :partition, :rpartition, :encoding, :force_encoding, :b, :valid_encoding?, :ascii_only?, :unpack, :encode, :encode!, :to_r, :to_c, :>, :>=, :<, :<=, :between?, :nil?, :!~, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
t.method(:upcase).call # => "HELLO"
t.methods
ise String'den türeyen t
ye ait tüm method’ları listeledik.
Sonuç Array (Dizi) olarak geldi ve bu dizinin tüm elemanları :
işaretiyle başlıyor. Çünkü bu elemanlar birer Symbol.
t.method(:upcase).call
da ise, t
'nin :upcase
method’unu call
ile
çağırdır. Aslında yaptığımız iş: "hello".upcase
ile birebir aynı.
Acaba bu nesne ne?
t.is_a?(String) # => true
is_a?
method’u ile nesnenin türünü kontrol
edebiliriz.
Diğer dillerin pek çoğunda (özellikle Python) bir işi yapmanın bir ya da en fazla iki yolu varken, Ruby bu konuda çok rahattır. Bir işi yapmanın her zaman birden fazla yolu olur ve bunların neredeyse hepsi doğrudur. (Kullanıldığı yere ve amaca bağlı olarak)
is_a?
yerine kind_of?
da kullanabiliriz!
Bir nesneye ait hangi method'ların olduğunu;
o = Object.new
o.methods # => [:nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
o.public_methods # => [:nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
o.private_methods # => [:initialize_copy, :initialize_dup, :initialize_clone, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :warn, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :eval, :local_variables, :iterator?, :block_given?, :catch, :throw, :loop, :respond_to_missing?, :trace_var, :untrace_var, :at_exit, :syscall, :open, :printf, :print, :putc, :puts, :gets, :readline, :select, :readlines, :`, :p, :test, :srand, :rand, :trap, :exec, :fork, :exit!, :system, :spawn, :sleep, :exit, :abort, :load, :require, :require_relative, :autoload, :autoload?, :proc, :lambda, :binding, :caller, :caller_locations, :Rational, :Complex, :set_trace_func, :gem, :gem_original_require, :initialize, :singleton_method_added, :singleton_method_removed, :singleton_method_undefined, :method_missing]
o.protected_methods # => []
o.public_methods(false) # => []
public_methods
default olarak public_methods(all=true)
şeklinde çalışır.
Eğer parametre olarak false
geçersek ve bu bizim oluşturduğumuz bir nesne
ise, sadece ilgili nesnenin public_method'ları geri döner.
Başta belirttiğimiz gibi, basit bir nesne bile Object'den türediği için ve
default olarak bu türeme esnasında tüm özellikler diğerine geçtiği için,
sadece sizin method'larınızı görüntülemek açısından false
olayı çok işe
yarar.
Method Missing
Bence Ruby'nin en süper özelliklerinden biridir. Olmayan bir method'u
çağırdığınız zaman tetiklenen method method_missing
method'udur. Ruby on
Rails framework'ü neredeyse bu mekanizma üzerine kurulmuştur. 3 parametre
alır; çağırılan method, eğer parametre geçilmişse parametreler, eğer block
geçilmişse block.
class User
def method_missing(method_name, *args, &block)
if method_name == :show_user_info
"This user has no information"
else
"You've called #{method_name}, You've passed: #{args}"
end
end
end
u = User.new
u.show_user_info # => "This user has no information"
u.show_user_age # => "You've called show_user_age, You've passed: []"
User
adında bir Class'ımız var. İçinde hiçbir method tanımlı değil.
u.show_user_info
satırında, olmayan bir method'u çağırıyoruz. Tanımladığımız
method_missing
method'u ile olmayan method çağırılmasını yakalıyoruz.
Eğer show_user_info
diye bir method çağrılırsa yakalıyoruz, bunun dışında
bir şey olursa da method adını ve geçilen parametreleri gösteriyoruz.
Bu sayede NoMethodError
hatası almadan işimize devam edebiliyoruz.
Anlamak açısından, Roman rakamları için bir sınıf yaptığınızı düşünün. Sadece örnek olması için gösteriyorum, C,X ve M için;
class Roman
def roman_to_str(str)
case str
when "x", "X"
10
when "c", "C"
100
when "m", "M"
1000
end
end
def method_missing(method)
roman_to_str method.id2name
end
end
r = Roman.new
r.x # => 10
r.X # => 10
r.C # => 100
r.M # => 1000
Bunu geliştirip "MMCX" ya da "III" gibi gerçek dönüştürme işini yapabilirsiniz.
respond_to_missing?
Yukarıdaki örnekte, olmayan method'ları ürettik. Peki, acaba bu olmayan
method'ları nasıl çağırabilir ya da kontrol edebiliriz? Normalde, bir Class'ın
hangi method'u olduğunu respond_to?
ile öğreniyorduk. Örneğe uygulayalım;
r.C
derken aslında :C
method'unu çağırıyoruz. Peki böyle bir method var mı?
r.method(:C) # => `method': undefined method `C' for class `Roman' (NameError)
Nasıl yani? peki kontrol edelim?
r.respond_to?(:C) # => false
Çünkü biz :C
yi dinamik olarak ürettik ama öylece ortada bıraktık. Yapmamız
gereken respond_to_missing?
ile gereken cevabı vermekti:
class Roman
def roman_to_str(str)
case str
when "x", "X"
10
when "c", "C"
100
when "m", "M"
1000
end
end
def method_missing(method)
roman_to_str method.id2name
end
def respond_to_missing?(method_name, include_private = false)
[:x, :X, :c, :C, :m, :M].include?(method_name) || super
end
end
r.method(:C) # => #<Method: Roman#C>
r.respond_to?(:C) # => true
r.respond_to?(:Q) # => false # olmayan method