Giriş: Ruby ve Nesne Tabanlı Programlama Felsefesi
Nesne Tabanlı Programlama (OOP), günümüz yazılım geliştirmenin en yaygın ve güçlü paradigmalarından biridir. Karmaşık sistemleri daha yönetilebilir, modüler ve ölçeklenebilir hale getirmek için tasarlanmıştır. Ruby, başından beri bu felsefeyi benimsemiş, saf nesne tabanlı bir dildir. Ruby'de her şey bir nesnedir – sayılar, dizeler, hatta fonksiyonlar bile! Bu yaklaşım, geliştiricilere doğal ve sezgisel bir programlama deneyimi sunar. Bu kapsamlı rehberde, Ruby'nin nesne tabanlı programlama yeteneklerini, temel prensiplerden ileri seviye konulara kadar detaylı bir şekilde inceleyeceğiz.
Nesne Tabanlı Programlamanın Temel İlkeleri:
Ruby'de Sınıflar ve Nesneler
Ruby'de her şey bir nesne olduğu için, programın temel yapı taşları sınıflar ve bu sınıflardan türetilen nesnelerdir. Bir sınıf, nesnelerin bir planıdır; nesneler ise bu planın somut örnekleridir.
Yukarıdaki örnekte, `Araba` bir sınıftır. `araba1` ve `araba2` ise bu sınıftan türetilmiş nesnelerdir. `@marka`, `@model`, `@yil` örnek değişkenleridir ve her nesneye özgüdür.
Kapsülleme (Encapsulation) ve Erişim Belirleyiciler
Kapsülleme, bir nesnenin iç durumunu (verisini) dış dünyadan saklama ve bu duruma yalnızca nesnenin kendi metodları aracılığıyla erişim sağlaması prensibidir. Ruby, varsayılan olarak tüm metodları `public` yapar. Ancak `private` ve `protected` anahtar kelimelerini kullanarak erişimi kısıtlayabiliriz.
Kalıtım (Inheritance)
Kalıtım, sınıflar arasında bir "bir türdür" ilişkisi kurar. Örneğin, "Otobüs bir tür araçtır." Bu, ortak özellikleri ve davranışları yeniden kullanmamızı sağlar. Ruby'de kalıtım, `class AltSinif < UstSinif` sözdizimi ile gerçekleştirilir.
Çok Biçimlilik (Polymorphism) ve Duck Typing
Polymorphism, farklı türdeki nesnelerin aynı arayüze sahip olabilmesi ve aynı mesajlara (metot çağrılarına) farklı şekillerde yanıt verebilmesidir. Ruby'de bu durum genellikle "Duck Typing" felsefesiyle açıklanır: "Eğer bir ördek gibi yürüyorsa ve bir ördek gibi vakvaklıyorsa, o zaman o bir ördektir." Yani, bir nesnenin belirli bir arayüze sahip olup olmadığı, hangi sınıfa ait olduğuna bakılmaksızın, o arayüzü uygulayan metotları içerip içermediğine göre belirlenir.
Soyutlama (Abstraction) ve Ruby Modülleri
Soyutlama, karmaşıklığı gizleyerek yalnızca temel bilgileri gösterme prensibidir. Ruby'de doğrudan soyut sınıflar veya arayüzler gibi kavramlar bulunmaz. Bunun yerine, bu tür soyutlamalar için Modüller (Modules) kullanılır. Modüller, metodları ve sabitleri gruplamak için bir yol sunar. Bir sınıf, birden fazla modülü `include` ederek o modüllerin metodlarını kendi bünyesine katabilir (mixin). Bu, Ruby'nin çoklu kalıtım sorununu çözmek için tercih ettiği yoldur.
Modüller aynı zamanda isim uzayı (namespace) olarak da kullanılabilir. Bu, farklı modüllerde aynı isimdeki sınıfların çakışmasını engeller.
Her Şey Bir Nesnedir!
Ruby'nin en temel ve güçlü özelliklerinden biri, her şeyin bir nesne olmasıdır. Sayılar, dizeler, diziler, hash'ler, hatta `nil` ve `true`/`false` bile nesnedir ve kendi metotlarına sahiptir.
İleri Seviye Konulara Kısa Bir Bakış
* Singleton Sınıflar (Metaclass'lar): Ruby'de bir nesneye dinamik olarak metot ekleyebilirsiniz. Bu metotlar sadece o nesneye özgüdür ve aslında nesnenin kendisi için oluşturulan gizli bir "singleton sınıfına" eklenir.
* Bloklar, Proc'lar ve Lambda'lar: Ruby'de fonksiyonel programlama öğeleri olan bloklar, kapatmalar ve Proc/Lambda nesneleri, OOP ile birleşerek güçlü ve esnek tasarımlara olanak tanır. Bir metodun davranışını çalışma zamanında değiştirmek veya bir dizi üzerinde iterasyon yapmak gibi pek çok senaryoda kullanılırlar.
İyi Uygulamalar ve Tasarım Desenleri
Ruby'de Nesne Tabanlı Programlama yaparken temiz, okunabilir ve sürdürülebilir kod yazmak için bazı iyi uygulamalar ve tasarım desenleri takip etmek faydalıdır:
Daha fazla bilgi için Ruby'nin resmi belgelerini buradan veya Stack Overflow gibi topluluk kaynaklarını buradan inceleyebilirsiniz.
Sonuç
Ruby, nesne tabanlı programlamanın temel ilkelerini doğal ve zarif bir şekilde uygulayan, dinamik ve esnek bir dildir. Sınıflar, nesneler, kalıtım, kapsülleme ve çok biçimlilik gibi temel kavramlar, Ruby'nin güçlü modül yapısı ve "her şey bir nesnedir" felsefesiyle birleştiğinde, geliştiricilere karmaşık yazılım sistemleri inşa etmek için mükemmel bir platform sunar. Bu rehber, Ruby'de nesne tabanlı düşünmeye başlamanız ve daha iyi, daha sürdürülebilir kod yazmanız için bir başlangıç noktası sağlamıştır.
Nesne Tabanlı Programlama (OOP), günümüz yazılım geliştirmenin en yaygın ve güçlü paradigmalarından biridir. Karmaşık sistemleri daha yönetilebilir, modüler ve ölçeklenebilir hale getirmek için tasarlanmıştır. Ruby, başından beri bu felsefeyi benimsemiş, saf nesne tabanlı bir dildir. Ruby'de her şey bir nesnedir – sayılar, dizeler, hatta fonksiyonlar bile! Bu yaklaşım, geliştiricilere doğal ve sezgisel bir programlama deneyimi sunar. Bu kapsamlı rehberde, Ruby'nin nesne tabanlı programlama yeteneklerini, temel prensiplerden ileri seviye konulara kadar detaylı bir şekilde inceleyeceğiz.
Nesne Tabanlı Programlamanın Temel İlkeleri:
- Kapsülleme (Encapsulation): Veriyi ve bu veri üzerinde çalışan metodları tek bir birim altında toplama ve dışarıdan doğrudan erişimi sınırlama prensibidir. Bu, veri bütünlüğünü korur ve kodun bakımını kolaylaştırır.
- Kalıtım (Inheritance): Bir sınıfın (alt sınıf/türetilmiş sınıf) başka bir sınıfın (üst sınıf/ana sınıf) özelliklerini ve davranışlarını miras almasıdır. Bu, kod tekrarını önler ve hiyerarşik bir yapı oluşturur.
- Çok Biçimlilik (Polymorphism): Farklı sınıflardaki nesnelerin aynı arayüze sahip olabilmesi ve aynı mesajlara farklı şekillerde yanıt verebilmesidir. Ruby'de Duck Typing ile güçlü bir şekilde desteklenir.
- Soyutlama (Abstraction): Karmaşık detayları gizleyerek yalnızca gerekli bilgileri sunma prensibidir. Kullanıcının sadece ne olduğunu bilmesi yeterlidir, nasıl çalıştığını bilmesi gerekmez.
Ruby'de Sınıflar ve Nesneler
Ruby'de her şey bir nesne olduğu için, programın temel yapı taşları sınıflar ve bu sınıflardan türetilen nesnelerdir. Bir sınıf, nesnelerin bir planıdır; nesneler ise bu planın somut örnekleridir.
Kod:
class Araba
# initialize metodu, bir nesne oluşturulduğunda otomatik çağrılır
def initialize(marka, model, yil)
@marka = marka # @ ile başlayan değişkenler örnek değişkenleridir (instance variables)
@model = model
@yil = yil
puts "[i]Yeni bir araba nesnesi oluşturuldu.[/i]"
end
# Örnek metot (instance method)
def bilgileri_goster
"Marka: #{@marka}, Model: #{@model}, Yıl: #{@yil}"
end
# Sınıf metodu (class method) - Sınıfın kendisine aittir
def self.kural
"[b]Her araba bir ulaşım aracıdır.[/b]"
end
end
# Nesneler oluşturma
araba1 = Araba.new("Toyota", "Corolla", 2020)
araba2 = Araba.new("Honda", "Civic", 2022)
puts araba1.bilgileri_goster
puts araba2.bilgileri_goster
puts Araba.kural # Sınıf metodunu çağırma
Yukarıdaki örnekte, `Araba` bir sınıftır. `araba1` ve `araba2` ise bu sınıftan türetilmiş nesnelerdir. `@marka`, `@model`, `@yil` örnek değişkenleridir ve her nesneye özgüdür.
Kapsülleme (Encapsulation) ve Erişim Belirleyiciler
Kapsülleme, bir nesnenin iç durumunu (verisini) dış dünyadan saklama ve bu duruma yalnızca nesnenin kendi metodları aracılığıyla erişim sağlaması prensibidir. Ruby, varsayılan olarak tüm metodları `public` yapar. Ancak `private` ve `protected` anahtar kelimelerini kullanarak erişimi kısıtlayabiliriz.
Kod:
class BankaHesabi
def initialize(bakiye)
@bakiye = bakiye
puts "[i]Yeni hesap oluşturuldu. Başlangıç bakiyesi: #{@bakiye}[/i]"
end
# Public metot
def bakiye_sorgula
"Mevcut bakiyeniz: #{@bakiye} TL"
end
# Public metot
def para_yatir(miktar)
if miktar > 0
@bakiye += miktar
"[b]#{miktar} TL yatırıldı.[/b] Yeni bakiye: #{@bakiye} TL"
else
"[i]Yatırılacak miktar pozitif olmalıdır.[/i]"
end
end
# Public metot
def para_cek(miktar)
if miktar > 0 && @bakiye >= miktar
@bakiye -= miktar
"[b]#{miktar} TL çekildi.[/b] Yeni bakiye: #{@bakiye} TL"
elsif miktar <= 0
"[i]Çekilecek miktar pozitif olmalıdır.[/i]"
else
"[i]Yetersiz bakiye![/i]"
end
end
private # Bu noktadan sonra tanımlanan metodlar private olur
def _log_islemi(islem_tipi, miktar)
puts "[u]#{Time.now}: #{islem_tipi} işlemi yapıldı, miktar: #{miktar}[/u]"
end
protected # Bu noktadan sonra tanımlanan metodlar protected olur
def _karsilastir_bakiye(diger_hesap)
@bakiye <=> diger_hesap.bakiye_bilgisi # protected metotlara sadece miras alınan sınıflar veya aynı sınıfın örnekleri erişebilir
end
end
hesap1 = BankaHesabi.new(1000)
puts hesap1.bakiye_sorgula
puts hesap1.para_yatir(500)
puts hesap1.para_cek(200)
# hesap1._log_islemi("TEST", 10) # Hata verir, çünkü _log_islemi private
Kalıtım (Inheritance)
Kalıtım, sınıflar arasında bir "bir türdür" ilişkisi kurar. Örneğin, "Otobüs bir tür araçtır." Bu, ortak özellikleri ve davranışları yeniden kullanmamızı sağlar. Ruby'de kalıtım, `class AltSinif < UstSinif` sözdizimi ile gerçekleştirilir.
Kod:
class Hayvan
def initialize(ad)
@ad = ad
end
def konus
"[i]Bilinmeyen bir ses çıkarıyor...[/i]"
end
def kendini_tanit
"Ben bir hayvanım, adım #{@ad}."
end
end
class Kedi < Hayvan # Kedi sınıfı Hayvan sınıfından miras alır
def initialize(ad, renk)
super(ad) # Üst sınıfın initialize metodunu çağırır
@renk = renk
end
# Metodu ezme (Method Overriding)
def konus
"Miyav! Miyav!"
end
def kendini_tanit
"[b]#{super}[/b] ve ben #{@renk} renkli bir kediyim." # super ile üst sınıf metodunu çağırma
end
end
class Kopek < Hayvan # Kopek sınıfı da Hayvan sınıfından miras alır
def konus
"Hav hav!"
end
end
kedi = Kedi.new("Tekir", "siyah")
kopek = Kopek.new("Fındık")
puts kedi.konus
puts kopek.konus
puts kedi.kendini_tanit
puts kopek.kendini_tanit
Çok Biçimlilik (Polymorphism) ve Duck Typing
Polymorphism, farklı türdeki nesnelerin aynı arayüze sahip olabilmesi ve aynı mesajlara (metot çağrılarına) farklı şekillerde yanıt verebilmesidir. Ruby'de bu durum genellikle "Duck Typing" felsefesiyle açıklanır: "Eğer bir ördek gibi yürüyorsa ve bir ördek gibi vakvaklıyorsa, o zaman o bir ördektir." Yani, bir nesnenin belirli bir arayüze sahip olup olmadığı, hangi sınıfa ait olduğuna bakılmaksızın, o arayüzü uygulayan metotları içerip içermediğine göre belirlenir.
Kod:
class Kus
def ucus
"Kanat çırparak uçuyor."
end
end
class Ucak
def ucus
"Motor gücüyle havalanıyor."
end
end
class Superman
def ucus
"Özel güçleriyle süzülüyor."
end
end
def ucur(nesne)
nesne.ucus
end
kus = Kus.new
ucak = Ucak.new
superman = Superman.new
puts ucur(kus)
puts ucur(ucak)
puts ucur(superman)
[quote]
"Ruby'nin Duck Typing özelliği, kodunuzu daha esnek ve daha az bağımlı hale getirir. Belirli bir sınıf tipine bağlı kalmak yerine, nesnelerin belirli bir davranışa (metoda) sahip olup olmadığına odaklanırsınız."
[/quote]
Soyutlama (Abstraction) ve Ruby Modülleri
Soyutlama, karmaşıklığı gizleyerek yalnızca temel bilgileri gösterme prensibidir. Ruby'de doğrudan soyut sınıflar veya arayüzler gibi kavramlar bulunmaz. Bunun yerine, bu tür soyutlamalar için Modüller (Modules) kullanılır. Modüller, metodları ve sabitleri gruplamak için bir yol sunar. Bir sınıf, birden fazla modülü `include` ederek o modüllerin metodlarını kendi bünyesine katabilir (mixin). Bu, Ruby'nin çoklu kalıtım sorununu çözmek için tercih ettiği yoldur.
Kod:
module Kaydedilebilir
def kaydet
"[b]#{self.class.name} nesnesi kaydedildi.[/b] (ID: #{object_id})"
end
def sil
"[i]#{self.class.name} nesnesi silindi.[/i] (ID: #{object_id})"
end
end
module Raporlanabilir
def rapor_olustur
"[u]#{self.class.name} için bir rapor oluşturuldu.[/u]"
end
end
class Urun
include Kaydedilebilir # Kaydedilebilir modülünü dahil et
include Raporlanabilir # Raporlanabilir modülünü dahil et
attr_accessor :ad, :fiyat
def initialize(ad, fiyat)
@ad = ad
@fiyat = fiyat
end
end
class Kullanici
include Kaydedilebilir # Kaydedilebilir modülünü dahil et
attr_accessor :kullanici_adi
def initialize(kullanici_adi)
@kullanici_adi = kullanici_adi
end
end
urun1 = Urun.new("Dizüstü Bilgisayar", 15000)
kullanici1 = Kullanici.new("ayse_demir")
puts urun1.kaydet
puts urun1.rapor_olustur
puts kullanici1.kaydet
puts kullanici1.sil
# urun1.sil # Çalışır, çünkü Kaydedilebilir dahil edildi.
# kullanici1.rapor_olustur # Hata verir, çünkü Kullanici sınıfı Raporlanabilir modülünü dahil etmedi.
Modüller aynı zamanda isim uzayı (namespace) olarak da kullanılabilir. Bu, farklı modüllerde aynı isimdeki sınıfların çakışmasını engeller.
Kod:
module Veritabani
class Baglanti
def initialize
puts "Veritabanı bağlantısı kuruldu."
end
end
end
module Ag
class Baglanti
def initialize
puts "Ağ bağlantısı kuruldu."
end
end
end
db_baglanti = Veritabani::Baglanti.new
ag_baglanti = Ag::Baglanti.new
Her Şey Bir Nesnedir!
Ruby'nin en temel ve güçlü özelliklerinden biri, her şeyin bir nesne olmasıdır. Sayılar, dizeler, diziler, hash'ler, hatta `nil` ve `true`/`false` bile nesnedir ve kendi metotlarına sahiptir.
Kod:
sayi = 10
metin = "Merhaba Dünya"
dizi = [1, 2, 3]
puts sayi.class # Integer
puts metin.class # String
puts dizi.class # Array
puts sayi.methods.grep(/to_s/) # Bir sayının string'e dönüştürme metotlarını göster
puts metin.upcase # Dizeyi büyük harflere çevir
puts dizi.length # Dizinin uzunluğunu al
[quote]
"Ruby'de 'her şey bir nesnedir' felsefesi, dilin tutarlılığını ve esnekliğini artırır. Geliştiricilere her veri türüyle benzer bir şekilde etkileşim kurma olanağı sunar."
[/quote]
İleri Seviye Konulara Kısa Bir Bakış
* Singleton Sınıflar (Metaclass'lar): Ruby'de bir nesneye dinamik olarak metot ekleyebilirsiniz. Bu metotlar sadece o nesneye özgüdür ve aslında nesnenin kendisi için oluşturulan gizli bir "singleton sınıfına" eklenir.
Kod:
nesne = Object.new
def nesne.hello
"Merhaba, ben sadece bu nesneye özgüyüm!"
end
puts nesne.hello # Çalışır
# diger_nesne = Object.new
# diger_nesne.hello # Hata verir
* Bloklar, Proc'lar ve Lambda'lar: Ruby'de fonksiyonel programlama öğeleri olan bloklar, kapatmalar ve Proc/Lambda nesneleri, OOP ile birleşerek güçlü ve esnek tasarımlara olanak tanır. Bir metodun davranışını çalışma zamanında değiştirmek veya bir dizi üzerinde iterasyon yapmak gibi pek çok senaryoda kullanılırlar.
Kod:
def metod_calistir(&blok)
puts "Metot içindeyim..."
blok.call("Dünya") if block_given?
puts "Metot bitti."
end
metod_calistir do |isim|
puts "Merhaba #{isim}!"
end
my_proc = Proc.new { |a, b| a + b }
puts my_proc.call(5, 3)
my_lambda = -> (x, y) { x * y }
puts my_lambda.call(4, 6)
İyi Uygulamalar ve Tasarım Desenleri
Ruby'de Nesne Tabanlı Programlama yaparken temiz, okunabilir ve sürdürülebilir kod yazmak için bazı iyi uygulamalar ve tasarım desenleri takip etmek faydalıdır:
- SRP (Single Responsibility Principle): Her sınıfın veya modülün yalnızca tek bir sorumluluğu olmalıdır.
- Open/Closed Principle: Yazılım varlıkları (sınıflar, modüller, fonksiyonlar vb.) geliştirmeye açık, ancak değiştirmeye kapalı olmalıdır.
- Kompazisyonu Kalıtıma Tercih Etmek: Genellikle "has-a" ilişkileri ("bir şeye sahiptir") için kalıtım yerine kompozisyon (nesneleri birleştirme) tercih edilir. Bu, esnekliği artırır ve sıkı bağımlılıkları azaltır.
- Test Edilebilirlik: Nesnelerinizi bağımsız olarak test edilebilir şekilde tasarlayın.
- Yeterince Erken Soyutlama (Just Enough Abstraction): Aşırı soyutlamadan kaçının; ihtiyaç duyduğunuzda soyutlama yapın.
Daha fazla bilgi için Ruby'nin resmi belgelerini buradan veya Stack Overflow gibi topluluk kaynaklarını buradan inceleyebilirsiniz.
Sonuç
Ruby, nesne tabanlı programlamanın temel ilkelerini doğal ve zarif bir şekilde uygulayan, dinamik ve esnek bir dildir. Sınıflar, nesneler, kalıtım, kapsülleme ve çok biçimlilik gibi temel kavramlar, Ruby'nin güçlü modül yapısı ve "her şey bir nesnedir" felsefesiyle birleştiğinde, geliştiricilere karmaşık yazılım sistemleri inşa etmek için mükemmel bir platform sunar. Bu rehber, Ruby'de nesne tabanlı düşünmeye başlamanız ve daha iyi, daha sürdürülebilir kod yazmanız için bir başlangıç noktası sağlamıştır.