Ruby'de metotların üç görünürlük düzeyi vardır. public varsayılandır ve metot her yerden çağrılabilir. private metot yalnızca sınıfın içinden, alıcı yazmadan ya da alıcı olarak doğrudan self yazılarak çağrılabilir. protected metot ise aynı sınıftan (ya da alt sınıflarından) bir nesnenin, başka bir nesne üzerinde çağırabileceği metottur. Kurala uymayan her çağrı NoMethodError verir.

Bu yazıda üç düzeyi, Ruby 2.7 ile değişen self.metot kuralını, sabitleri gizleyen private_constant'ı, yapıcıyı gizleyen private_class_method'u ve görünürlüğü atlayan send ile atlamayan public_send farkını Ruby 4.0.9'da çalıştırılmış örneklerle anlatıyoruz. Sınıf ve modül temelleri için Ruby class yazısına ve Ruby module yazısına bakabilirsiniz.

private: üç yazım biçimi

private: üç yazım biçimi

Argümansız private yazdığınızda ondan sonra tanımlanan bütün metotlar private olur. private :ad var olan bir metodu private yapar. def ifadesi metot adını sembol olarak döndürdüğü için private def ad biçimi de çalışır.

ruby
class Sifreleyici
  def kaydet(parola)
    "kaydedildi: #{ozet(parola)}"
  end

  private

  def ozet(metin)
    metin.reverse
  end
end

s = Sifreleyici.new
p s.kaydet("abc123")    # çıktı: "kaydedildi: 321cba"
s.ozet("abc")
# NoMethodError: private method 'ozet' called for an instance of Sifreleyici

p Sifreleyici.private_instance_methods(false)  # çıktı: [:ozet]
p Sifreleyici.public_instance_methods(false)   # çıktı: [:kaydet]

class Rapor
  private def hesapla = 42
  def sonuc = hesapla
end
p Rapor.new.sonuc   # çıktı: 42

private metot ve self: hangi çağrı geçerli?

Belgelerdeki kural şu: private metot alıcı olmadan ya da alıcı olarak doğrudan yazılmış self ile çağrılabilir. Ruby 2.7 NEWS dosyası, private metodu self.metot biçiminde çağırmaya izin verilmesinin o sürümde geldiğini belirtiyor. Ancak self'i bir değişkene atayıp o değişken üzerinden çağırmak ya da aynı sınıftan başka bir nesne üzerinde çağırmak hâlâ yasak:

ruby
class A
  def receiversiz = m
  def self_ile = self.m
  def baska_nesne = A.new.m

  def kopya_ile
    kopya = self
    kopya.m
  end

  private

  def m = 1
end

a = A.new
p a.receiversiz   # çıktı: 1
p a.self_ile      # çıktı: 1
a.baska_nesne
# NoMethodError: private method 'm' called for an instance of A
a.kopya_ile
# NoMethodError: private method 'm' called for an instance of A

Bu kural private yazıcılar için de önemlidir. Bir metodun içinde mod = "test" yazarsanız Ruby bunu yerel değişken ataması sayar; private mod= yazıcısını çağırmak için self.mod = "test" yazmanız gerekir. attr_accessor tanımladığı metot adlarını dizi olarak döndürdüğü için private attr_accessor :mod yazımı da çalışır:

ruby
class Ayarlar
  def initialize
    self.mod = "test"
  end

  def goster = mod

  private attr_accessor :mod
end

p Ayarlar.new.goster   # çıktı: "test"
p Ayarlar.private_instance_methods(false).sort  # çıktı: [:initialize, :mod, :mod=]

Çıktıda initialize metodunun da listelendiğine dikkat edin: Ruby bu metodu kendiliğinden private yapar, bu yüzden nesne.initialize diye çağrılamaz.

Alt sınıflar private metotları görebilir

Java gibi dillerden gelenler için şaşırtıcı bir nokta: Ruby'de alt sınıf, üst sınıfın private metotlarını alıcı yazmadan çağırabilir. Module#protected belgesi bunu açıkça belirtiyor. Yani Ruby'deki private, sınıf hiyerarşisini değil alıcıyı sınırlar.

ruby
class Ebeveyn
  private

  def gizli = "ebeveynin private metodu"
end

class Cocuk < Ebeveyn
  def goster = gizli
end

p Cocuk.new.goster   # çıktı: "ebeveynin private metodu"

protected: aynı türden nesneler arasında

protected: aynı türden nesneler arasında: public, protected, private

protected metot, metodu çağıran kod aynı sınıfın (ya da bir alt sınıfın) içindeyse başka bir nesne üzerinde de çağrılabilir. En yaygın kullanımı, iç durumu herkese açmadan iki nesneyi karşılaştırmaktır. Aşağıda bakiye dışarıdan okunamıyor ama > metodu içinde diger.bakiye çağrısı geçerli:

ruby
class Hesap
  attr_reader :bakiye
  protected :bakiye

  def initialize(bakiye)
    @bakiye = bakiye
  end

  def >(diger)
    bakiye > diger.bakiye
  end
end

h1 = Hesap.new(100)
h2 = Hesap.new(50)
p h1 > h2   # çıktı: true
h1.bakiye
# NoMethodError: protected method 'bakiye' called for an instance of Hesap

class Musteri
  def zengin_mi?(hesap) = hesap.bakiye > 75
end
Musteri.new.zengin_mi?(h1)
# NoMethodError: protected method 'bakiye' called for an instance of Hesap

Musteri sınıfı Hesap'tan türemediği için onun içinden yapılan çağrı reddedildi. Belgeler ayrıca protected metotların satır içi önbelleği kullanamadığı için diğerlerinden yavaş olduğunu not ediyor; bu yüzden yalnızca gerçekten gerektiğinde tercih edilmesi mantıklı.

GörünürlükAlıcısız çağrıself.metotBaşka nesne üzerindeSınıf dışından
publicEvetEvetEvetEvet
protectedEvetEvetAynı sınıf ailesinin içinden evetHayır
privateEvetEvet (Ruby 2.7 ve sonrası)HayırHayır

private_class_method ve private_constant

Görünürlük yalnızca instance metotları için değildir. private_class_method :new sınıfın yapıcısını gizler; nesneyi yalnızca sizin yazdığınız bir fabrika metodu üretebilir. private_constant ise bir sabite sınıf dışından Sinif::SABIT biçiminde erişilmesini engeller; sınıfın içindeki kod sabiti kullanmaya devam eder.

ruby
class Baglanti
  private_class_method :new

  def self.ac(adres)
    new(adres)
  end

  def initialize(adres)
    @adres = adres
  end
end

p Baglanti.ac("db.local").instance_variable_get(:@adres)  # çıktı: "db.local"
Baglanti.new("x")
# NoMethodError: private method 'new' called for class Baglanti

class Odeme
  API_ANAHTARI = "sk_test_123"
  private_constant :API_ANAHTARI
  SURUM = "v2"

  def self.baslik = "Bearer #{API_ANAHTARI}"
end

p Odeme.baslik      # çıktı: "Bearer sk_test_123"
p Odeme::SURUM      # çıktı: "v2"
p Odeme.constants   # çıktı: [:SURUM]
Odeme::API_ANAHTARI
# NameError: private constant Odeme::API_ANAHTARI referenced

send ve public_send farkı

send metot adını sembol ya da string olarak alıp o metodu çağırır ve görünürlüğe bakmaz. public_send ise yalnızca public metotları çağırır; private bir metoda ulaşmaya çalışırsa normal çağrıdaki gibi NoMethodError verir. Metot adı kullanıcıdan ya da dışarıdan gelen bir veriden üretiliyorsa public_send kullanmak, private metotların istemeden açılmasını önler.

ruby
class Kasa
  def bakiye = 500

  private

  def sifre = "1234"
end

k = Kasa.new
p k.send(:bakiye)          # çıktı: 500
p k.send(:sifre)           # çıktı: "1234"
p k.public_send(:bakiye)   # çıktı: 500
k.public_send(:sifre)
# NoMethodError: private method 'sifre' called for an instance of Kasa

p k.respond_to?(:sifre)        # çıktı: false
p k.respond_to?(:sifre, true)  # çıktı: true
p k.__send__("bakiye")         # çıktı: 500

respond_to? varsayılan olarak private ve protected metotları saymaz; ikinci argümana true verirseniz onları da hesaba katar. Belgeler, nesnenin kendi send adlı bir metodu olabileceği durumlar için BasicObject'in tanımladığı __send__ adını daha güvenli seçenek olarak gösteriyor.

Sık yapılan hatalar

Sık yapılan hatalar: private yazıcıyı self olmadan çağırmak, private'ı Java'daki gibi düşünmek, Dış veriyle send kullanmak, private'ın sınıf metotlarını etkilediğini sanmak
  • private yazıcıyı self olmadan çağırmak: mod = 1 yerel değişken oluşturur; self.mod = 1 yazın.
  • private'ı Java'daki gibi düşünmek: Alt sınıflar private metotları çağırabilir; sınırlanan şey alıcıdır.
  • Dış veriyle send kullanmak: nesne.send(params[:islem]) gibi bir çağrı private metotları da açar; public_send ve bir izin listesi kullanın.
  • private'ın sınıf metotlarını etkilediğini sanmak: Argümansız private satırı def self.x ile tanımlanan sınıf metotlarını private yapmaz. Bunun için private_class_method kullanılır.

Sık Sorulan Sorular

private metodu test etmek için nasıl çağırırım?

send görünürlüğe bakmadığı için private metodu doğrudan çağırabilir. Yine de genellikle private metotları onları kullanan public metotlar üzerinden test etmek daha sağlıklıdır.

Görünürlüğü sonradan değiştirebilir miyim?

Evet. public :ad, private :ad ve protected :ad var olan metotların görünürlüğünü değiştirir. Belgelere göre tek argüman verildiğinde aynı argüman, birden fazla verildiğinde dizi döndürülür.

Modül fonksiyonları neden private görünüyor?

module_function ile tanımlanan metotların mixin kopyası private yapılır. Ayrıntılar Ruby module yazısında.

Karşılaştırma metotlarında protected yerine ne kullanılabilir?

Karşılaştırma için çoğu zaman <=> tanımlayıp Comparable eklemek yeterlidir. Bu yaklaşım Comparable ve Enumerable 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 class ve nesneler · Ruby modülleri · Ruby metot tanımlama · Comparable ve Enumerable · begin, rescue, ensure