Ruby'de bir sınıf class Ad ... end bloğuyla tanımlanır; Ad.new(...) çağrısı yeni bir nesne oluşturur ve ona verdiğiniz argümanlarla initialize metodunu çalıştırır. Nesnenin verisi @ad gibi instance değişkenlerinde durur. Bu değişkenleri dışarıya açmak için attr_reader (okuma), attr_writer (yazma) ve attr_accessor (ikisi birden) kullanılır. Bir sınıf başka bir sınıftan < ile türetilir ve üst sınıftaki aynı adlı metot super ile çağrılır.

Bu yazıda sınıf tanımlamayı, instance metodu ile sınıf metodu farkını, kalıtımı, super kullanımının üç biçimini ve nesnenin ekranda nasıl göründüğünü belirleyen to_s ile inspect metotlarını Ruby 4.0.9 ile çalıştırılmış örneklerle anlatıyoruz. Ruby'nin ne olduğunu ve nerede kullanıldığını merak ediyorsanız önce Ruby nedir yazısına, metot tanımlamanın temelleri için Ruby metot tanımlama rehberine bakabilirsiniz.

Sınıf tanımlamak, initialize ve attr metotları

Sınıf tanımlamak, initialize ve attr metotları: attr_reader :ad, attr_writer :stok, attr_accessor :fiyat, attr :ad

Aşağıdaki Urun sınıfı üç bilgi tutuyor. initialize içinde argümanları instance değişkenlerine yazıyoruz. attr_reader :ad yalnızca ad okuyucusunu, attr_accessor :fiyat hem fiyat hem fiyat= metotlarını, attr_writer :stok ise yalnızca stok= yazıcısını üretir.

ruby
class Urun
  attr_reader :ad
  attr_accessor :fiyat
  attr_writer :stok

  def initialize(ad, fiyat, stok = 0)
    @ad = ad
    @fiyat = fiyat
    @stok = stok
  end

  def stokta_var_mi?
    @stok > 0
  end
end

klavye = Urun.new("Klavye", 750)
p klavye.ad               # çıktı: "Klavye"
klavye.fiyat = 690
p klavye.fiyat            # çıktı: 690
p klavye.stokta_var_mi?   # çıktı: false
klavye.stok = 3
p klavye.stokta_var_mi?   # çıktı: true
p klavye.instance_variables  # çıktı: [:@ad, :@fiyat, :@stok]

klavye.ad = "Fare"
# NoMethodError: undefined method 'ad=' for an instance of Urun
klavye.stok
# NoMethodError: undefined method 'stok' for an instance of Urun

Hata mesajları bu üç metodun farkını özetliyor: ad için yazıcı, stok için okuyucu üretilmediği için çağrılar NoMethodError veriyor. Instance değişkenleri dışarıdan doğrudan görülmez; nesnenin verisine yalnızca tanımladığınız metotlar üzerinden erişilir.

Ruby 3.0'dan beri attr_accessor, attr_reader ve attr_writer tanımladıkları metot adlarını bir dizi olarak döndürür. Bu küçük ayrıntı, ileride göreceğiniz private attr_accessor :x yazımını mümkün kılar:

ruby
class Ornek
  p attr_accessor(:a, :b)   # çıktı: [:a, :a=, :b, :b=]
  p attr_reader(:c)         # çıktı: [:c]
end
MetotÜrettiği metotlarNe zaman
attr_reader :adadDeğer dışarıdan okunacak ama değiştirilmeyecekse
attr_writer :stokstok=Değer yalnızca dışarıdan atanacaksa (seyrek)
attr_accessor :fiyatfiyat ve fiyat=Hem okunup hem yazılacaksa
attr :adadattr_reader ile aynı (tek argümanlı biçim)

Instance metodu ve sınıf metodu

def ad ile tanımlanan metot o sınıftan üretilen her nesnede çalışır; buna instance metodu denir. def self.ad ile tanımlanan metot ise sınıf nesnesinin kendisine aittir ve Siparis.ad biçiminde çağrılır. Ruby'de sınıflar da birer nesnedir: Urun.class ifadesi Class döndürür. Birden fazla sınıf metodunu bir arada yazmak isterseniz class << self bloğunu kullanabilirsiniz; bu blok sınıfın singleton sınıfını açar.

ruby
class Siparis
  KDV = 20
  @adet = 0

  class << self
    attr_reader :adet

    def olustur(tutar)
      @adet += 1
      new(tutar)
    end
  end

  def self.kdv_dahil(tutar)
    tutar + tutar * KDV / 100
  end

  attr_reader :tutar

  def initialize(tutar)
    @tutar = tutar
  end

  def toplam
    self.class.kdv_dahil(tutar)
  end
end

s1 = Siparis.olustur(100)
s2 = Siparis.olustur(250)
p Siparis.adet            # çıktı: 2
p Siparis.kdv_dahil(50)   # çıktı: 60
p s2.toplam               # çıktı: 300
p Siparis.singleton_methods.sort  # çıktı: [:adet, :kdv_dahil, :olustur]

s1.olustur(10)
# NoMethodError: undefined method 'olustur' for an instance of Siparis

Burada dikkat edilecek nokta, sınıf gövdesinde yazılan @adet = 0 satırının nesnelere değil sınıf nesnesine ait bir instance değişkeni oluşturmasıdır. Instance metodu içinden sınıf metoduna ulaşmak için self.class.kdv_dahil yazıyoruz. Sınıf metodu nesne üzerinden çağrılamaz; son satırdaki hata bunu gösteriyor.

Kalıtım ve super

class VadeliHesap < Hesap yazdığınızda VadeliHesap, Hesap sınıfındaki metotları ve sabitleri devralır. Üst sınıf belirtmezseniz sınıf Object'ten türer. Alt sınıfta aynı adla bir metot yazarsanız üsttekinin yerine o çalışır; üsttekini de kullanmak istediğinizde super çağırırsınız.

ruby
class Hesap
  attr_reader :sahip, :bakiye

  def initialize(sahip, bakiye)
    @sahip = sahip
    @bakiye = bakiye
  end

  def ozet
    "#{sahip}: #{bakiye} TL"
  end
end

class VadeliHesap < Hesap
  def initialize(sahip, bakiye, faiz)
    super(sahip, bakiye)
    @faiz = faiz
  end

  def ozet
    super + " (yıllık faiz %#{@faiz})"
  end
end

v = VadeliHesap.new("Ayşe", 1000, 40)
puts v.ozet                # çıktı: Ayşe: 1000 TL (yıllık faiz %40)
p VadeliHesap.superclass   # çıktı: Hesap
p VadeliHesap.ancestors    # çıktı: [VadeliHesap, Hesap, Object, Kernel, BasicObject]
p v.is_a?(Hesap)           # çıktı: true
p v.instance_of?(Hesap)    # çıktı: false
p Hesap.subclasses         # çıktı: [VadeliHesap]

is_a? kalıtım zincirinin tamamına bakarken instance_of? yalnızca nesnenin doğrudan sınıfını kontrol eder. ancestors listesi, Ruby'nin bir metodu ararken hangi sırayla sınıflara ve modüllere baktığını gösterir. Bu listeye modüllerin nasıl girdiğini Ruby module, include, extend ve prepend yazısında ayrıntılı anlatıyoruz.

super, super() ve super(arg) farkı

Belgelere göre parantezsiz super, alt sınıf metoduna gelen argümanların aynısını üst sınıfa iletir. super() hiç argüman göndermez, super(2) ise yalnızca sizin yazdığınızı gönderir. Bu fark, üst ve alt metodun parametre sayısı farklı olduğunda önem kazanır:

ruby
class A
  def selam(ad)
    "Merhaba #{ad}"
  end
end

class B < A
  def selam(ad)
    super + "!"      # ad otomatik olarak iletilir
  end
end

class C < A
  def selam(ad)
    super()          # hiç argüman gönderilmez
  end
end

p B.new.selam("Can")   # çıktı: "Merhaba Can!"
C.new.selam("Can")
# ArgumentError: wrong number of arguments (given 0, expected 1)

Yukarıdaki VadeliHesap#initialize örneğinde üç parametre alıp üst sınıfa yalnızca ikisini göndermemiz gerektiği için super(sahip, bakiye) yazdık. Parantezsiz super kullansaydık üç argüman iletilir ve ArgumentError oluşurdu.

to_s ve inspect: nesne nasıl görünür?

puts ve string içine gömme ("#{nesne}") nesnenin to_s metodunu, p ise inspect metodunu kullanır. Varsayılan inspect sınıf adını, bellek adresini temsil eden bir değeri ve instance değişkenlerini gösterir. Kendi sınıflarınızda bu iki metodu ezerek daha okunur bir çıktı elde edebilirsiniz.

ruby
class Musteri
  def initialize(ad, sehir)
    @ad = ad
    @sehir = sehir
  end

  def to_s
    "#{@ad} (#{@sehir})"
  end

  def inspect
    "#<Musteri #{@ad}>"
  end
end

m = Musteri.new("Elif", "İzmir")
puts m                  # çıktı: Elif (İzmir)
puts "Müşteri: #{m}"    # çıktı: Müşteri: Elif (İzmir)
p m                     # çıktı: #<Musteri Elif>
p [m, m]                # çıktı: [#<Musteri Elif>, #<Musteri Elif>]

Ruby 4.0 ile gelen bir yenilik, varsayılan inspect çıktısına hangi instance değişkenlerinin gireceğini seçebilmenizdir. NEWS dosyasına göre Kernel#inspect artık nesnede instance_variables_to_inspect metodu olup olmadığına bakıyor. Parola gibi hassas alanları günlüklerden uzak tutmak için kullanışlı:

ruby
class Ayar
  def initialize(host, kullanici, parola)
    @host = host
    @kullanici = kullanici
    @parola = parola
  end

  private

  def instance_variables_to_inspect = [:@host, :@kullanici]
end

p Ayar.new("localhost", "root", "s3cr3t")
# çıktı: #<Ayar:0x... @host="localhost", @kullanici="root">

Sık yapılan hatalar

Sık yapılan hatalar: Yazıcı yerine yerel değişken oluşturmak, initialize metodunu dışarıdan çağırmaya çalışmak, Yeniden açılan sınıfa farklı üst sınıf vermek, Sahibi olmadığınız...
  • Yazıcı yerine yerel değişken oluşturmak: Metot içinde deger = 5 yazmak deger= metodunu çağırmaz, yeni bir yerel değişken oluşturur. Yazıcıyı çağırmak için self.deger = 5 yazmanız gerekir.
  • initialize metodunu dışarıdan çağırmaya çalışmak: initialize private bir metottur; nesne.initialize çağrısı NoMethodError verir. Nesne oluşturmak için her zaman new kullanılır.
  • Yeniden açılan sınıfa farklı üst sınıf vermek: Sınıflar sonradan yeniden açılabilir, ama ilk tanımdan farklı bir üst sınıf yazarsanız TypeError: superclass mismatch alırsınız.
  • Sahibi olmadığınız sınıfları yeniden açmak: Belgeler bunun ad çakışmalarına ve zor bulunan hatalara yol açabileceği konusunda uyarıyor. Daha güvenli bir yol için Ruby refinements yazısına bakın.
ruby
class Sayac
  attr_accessor :deger

  def initialize
    @deger = 0
  end

  def hatali_artir
    deger = deger.to_i + 1   # yerel değişken, @deger değişmez
  end

  def dogru_artir
    self.deger += 1
  end
end

s = Sayac.new
s.hatali_artir
p s.deger   # çıktı: 0
s.dogru_artir
p s.deger   # çıktı: 1
s.initialize
# NoMethodError: private method 'initialize' called for an instance of Sayac

class C; end
class D < C; end
class D < String; end
# TypeError: superclass mismatch for class D

Sık Sorulan Sorular

Ruby'de her şey nesne mi?

Belgelerdeki tanımla, Ruby'de sınıflar da Class sınıfının örnekleridir; yani sınıfın kendisi de bir nesnedir. Bu yüzden sınıflara metot tanımlayabilir, onları değişkene atayabilir ve Class.new ile isimsiz sınıflar oluşturabilirsiniz.

Sınıf adı neden büyük harfle başlıyor?

class Urun yazdığınızda oluşan sınıf nesnesi Urun adlı bir sabite atanır. Ruby'de sabitler büyük harfle başlar; sabit kuralları için değişkenler ve sabitler yazısına bakabilirsiniz.

Sadece tek bir nesneye metot ekleyebilir miyim?

Evet. def nesne.metot ya da class << nesne yazdığınızda metot o nesnenin singleton sınıfına eklenir ve aynı sınıftaki diğer nesnelerde bulunmaz. Denememizde o.singleton_methods yalnızca bu metodu listeledi.

Çoklu kalıtım var mı?

Hayır, bir sınıfın tek bir üst sınıfı olur. Birden fazla yerden davranış almak için modüller include edilir. Sınıflar da modüldür ama bir sınıf başka bir sınıfa mixin olarak eklenemez.

Metotları gizlemek için ne yapmalıyım?

private ve protected anahtar sözcükleri metotların kimin tarafından çağrılabileceğini sınırlar. Kurallar ve tuzaklar Ruby public, private ve protected yazısında anlatılıyor.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Ruby öğrenme rehberi · Ruby metot tanımlama · Ruby modülleri · public, private, protected · Struct ve Data · Ruby nedir