Programming Ruby

The Pragmatic Programmer's Guide

Previous < Contents ^
Next >

Classes, Objects, and Variables



Şimdiye kadar gördüğümüz örneklere dayanarak; Ruby'nin nesneye- yönelik bir dil olduğuna dair iddialarımızı merak ediyor olmalısınız. Güzel, çünkü bu bölüm boyunca bu iddiayı açıklığa kavuşturmaya çalışacağız. Ruby'de sınıfları ve nesneleri nasıl yaratacağınıza ve Ruby'nin diğer nesneye yönelik programlama dillerinden hangi noktalarda üstün olduğuna bakacağız. Ayrıca, diğer milyon- dolarlık projelerimiz olan Internet Enabled Jazz ve Blue Grass jukebox'ı da geliştirmeye devam edeceğiz.

Aylarca çalışmadan sonra, yüksek ücretli Araştırma ve Geliştirme çalışanlarımız, müzik kutumuzun songslara ihtiyacı olduğunu tespit etti. Bu yüzden öncelikle şarkıları temsil eden bir Ruby sınıfı oluşturmamız daha doüru gibi gözüküyor. Gerçek bir şarkının ismi, yorumcusu ve bie süresi olduğunu biliyoruz, o yüzden programımızdaki şarkı nesnelerinin de bu özelliklere sahip olmasını sağlamalıyız.

Öncelikle sadece basit bir initialize metoduna sahip olan basit bir Song sınıfı yaratmakla başlıyoruz [Sayfa 9'da bahsettiğimiz gibi sınıf isimleri büyük harfle başlarken, metot isimleri küçük harfle başlıyor.]

class Song
  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
  end
end

initialize, Ruby programlarında bulunan özel bir metottur. Yeni bir Song nesnesi yaratmak için Song.new'i çağırdığınızda, Ruby hazırlanmamış bir nesne yaratır ve daha sonra new'e geçtiğimiz tüm parametrelerle birlikte nesnenin initialize metodunu çağırır. Bu, nesnenin durumunu belirleyebileceğiniz kodlar yazmanıza izin verir.

Song sınıfı için, initialize metodu üç parametre alır. Bu parametreler metodun içindeki yerel değişkenler gibi davrnırlar, bu yüzden de yerel değişkenleri adlandırma kuralına göre; yani küçük harfle başlayarak adlandırılmışlardır.

Her bir nesne bir şarkıyı temsil eder, bu yüzden her bir Song nesnesinin kendi şarkı adını, yorumcusunu ve süresini taşımasını sağlamalıyız. Bu da, bu değerleri nesnenin içine örnek değişkenler gibi yerleştirmemiz gerektiği anlamına gelir. Ruby'de bir örnek değişken, isminin başına bir salyangoz işareti (``@'') alarak oluşturulur. Bizim örneğimizde, name parametresi @name'e, artist @artist'e ve duration (şarkının saniye olarak uzunluğu)@duration örnek değişkenine atanmıştır.

Şimdi yeni sınıfımızı test edelim:

aSong = Song.new("Bicylops", "Fleck", 260)
aSong.inspect » "#<Song:0x401b4924 @duration=260, @artist=\"Fleck\", @name=\"Bicylops\">"

Güzel, çalışacak gibi görünüyor. Varsayılan olarak, inspect mesajı her nesneye gönderilebilinen bir mesajdır ve nesnenin id'si ile örnek değişkenlerini ortaya çıkarır. Hepsini doğru kurmuşuz gibi gözüküyor.

Deneyimlerimiz bize, geliştirme sırasında bir Song nesnesinin içeriğini defalarca ekrana yazdıracağımızı ve inspect 'in varsayılan biçiminin beklenen birşeyler bıraktığını söylüyor. Neyse ki, to_s adında, nesneye bir dizgi sunmak istediğini söyleyen standat bir mesajımız var. Şimdi bu mesajı kendi şarkımızda deneyelim:

aSong = Song.new("Bicylops", "Fleck", 260)
aSong.to_s » "#<Song:0x401b499c>"

Bu yeterince kullanışlı değil---sadece nesnenin id'sini bildirdi. O halde to_s'yi kendi sınıfımız için yeniden yazalım. Bunu yapmadan önce, bir kaç dakika bu kitap boyunca sınıfları nasıl göstereceğimizden bahsedelim:

Ruby'de sınıflar asla kapanmaz: her zaman için varolan bir sınıfa yeni metotlar ekleyebilirsiz. Bu hem sizin oluşturduğunuz sınıflar için hem de standart, built-in sınıflar için geçerlidir. Tek yapmanız gereken şey, varolan bir sınıf için bir sınıf tanımlaması açmak ve yeni özellikleri eklemektir.

Bu amaçlarımız için iyi bir durumdur. Bu bölüm boyunca, eski sınıf tanımlamalarımız hala dururken, onlara yeni özellikler ekleyip, yeni metotlar için sadece sınıf tanımlamasını göstermek işimize yarayacaktır. Bu bize, her örnekte gereksiz tanımlamalar yapmaktan koruyacaktır. Düşünün, eğer bu durumdan habersizce, bu kodu sıfırdan yazmanız gerekseydi, muhtemelen tüm metotları tek bir basit sınıf yapısına atardınız.

Bu kadar detay yeter! Şimdi Song sınıfımıza bir to_s metodu eklemeye devam edelim:

class Song
  def to_s
    "Song: #{@name}--#{@artist} (#{@duration})"
  end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.to_s » "Song: Bicylops--Fleck (260)"

Mükemmel, ilerleme kaydediyoruz. Ancak bazı şeyleri üstü kapalı olarak geçtik. Ruby'nin tüm nesneler için to_s'yi desteklediğini söyledik ancak bunu nasıl yaptığından bahsemedik. Bunun cevabını mirasla, altsınıflamayla ve bir nesneye bir mesaj gönderdiğinizde Ruby'nin bunu nasıl saptadığıyla açıklamamız gerekiyor. Bu da yeni bir bölüm demek, öyleyse...

Inheritance and Messages

Miras, başka bir sınıfı özelleştirerek ya da ek özellikler ekleyerek yeni bir sınıf yaratmanıza izin verir. örneğin, müzik kutumuz Song sınıfına yerleştirdiğimiz şarkı içeriğine sahiptir. Daha sonra pazarlama işi devreye girer ve müzik kutumuza karaoke desteği eklememiz gerektiğini söyler. Bir karaoke şarkısı da aynı diğerlerine benzer (sadece vokal yoktur, ama bu kısmı bizi ilgilendirmiyor). Ancak, zaman ve şarkı sözleriyle ilişkili bir bağlantıya sahiptir. Müzik kutumuz bir karaoke şarkısı çalarken şarkı sözleri, müzik kutusunun ekranında müzikle aynı anda gösterilmelidir.

Bu problemi halletmek için, tıpkı Song'a benzer, ancak şarkı sözü desteğiyle beraber KaraokeSong adında bir sınıf oluşturabiliriz.

class KaraokeSong < Song
  def initialize(name, artist, duration, lyrics)
    super(name, artist, duration)
    @lyrics = lyrics
  end
end

Sınıf tanımlamasındaki ``< Song'' satırı, Ruby'ye KaraokeSong'un, Song sınıfının bir alt sınıfı olduğunu belirtiyor. (Şaşırtıcı olmayan bir şekilde, bu ayrıca Song'un, KaraokeSong'un süper sınıfı olduğu anlamına gelir. İnsanlar ayrıca çocuk-ebeveyn ilişkinden bahsederler, bu durumda KaraokeSong'un ebeveyni Song sınıfı olacaktır.) Şimdilik, initialize metodu hakkında fazla kafa yormayın, bu super çağrıyla daha sonra ilgileneceğiz.

Şimdi bir KaraokeSong yaratalım ve kodumuzun çalışıp çalışmadığını görelim. (Son sistemde, tüm şarkı sözleri metin ve zaman bilgisini içeren bir nesnede tutulacaktır. Sınıfımızı test etmek için, sadece bir dizgi kullanacağız. Bu tipi olmayan (untyped) dillerin başka bir özelliğidir--kodu çalıştırmaya başlamadan önce herşeyi tanımlamak zorunda değilsiniz.)

aSong = KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s » "Song: My Way--Sinatra (225)"

Güzel, çalıştı; peki to_s metodu neden şarkı sözlerini göstermedi?

Bu sorunun cevabı, Ruby'nin bir nesneye bir mesaj gönderdiğinizde hangi metodun çağrılacağına nasıl karar verdiğiyle açıklanabilir. Ruby, aSong.to_s metodunu derlediğinde, aslında to_s metodunun tam olarak nerede bulacağını bilmez. Üstelik, karar vermeyi program çalışıncaya kadar erteler. Önce, aSong'un sınıfına bakar. Eğer bu sınıf, mesajla aynı adı içeren bir metot içeriyorsa, o metot çalıştırılır. Eğer bulunamazsa, bu metodu bir üst ebeveyninde arar, eğer bulumazsa onun da bir üst ebeveyninde; bu durum ata zincirinin en üstüne kadar devam eder. Eğer, atalarında da eşleşen bir metot bulamazsa, normalde hata verecek özel bir durumla karşılık verir. [Aslında, runtime sırasında bu hatanın önüne geçebilirsiniz. Daha fazla bilgi için, sayfa 355'deki Object#method_missing bölümüne bakın.]

Şimdi örneğimize geri dönelim. KaraokeSong sınıfının bir nesnesi olan aSong nesnesine to_s mesajını gönderdik. Ruby, KaraokeSong sınıfında, to_s adında bir metot aradı, fakat bulamadı. Bunun üzerine yorumlayıcı, KaraokeSong'un ebeveyni olan Song sınıfına bakıyor ve sayfa 18'de tanımladığımız to_s metodunu buluyor. İşte bu yüzden, şarkı detaylarını ekrana yazdığı halde, şarkı sözlerini yazmıyor-- Song sınıfı şarkı sözleri hakkında hiçbir bilgiye sahip değil.

Şimdi, KaraokeSong#to_s metodunu gerçekleştirelim. Bunu yapabilmek için birçok yol mevcut. Kötü bir yolla başlayalım. Song sınıfından to_s metodunu kopyalayalım ve şarkı sözünü ekleyelim.

class KaraokeSong
  # ...
  def to_s
    "KS: #{@name}--#{@artist} (#{@duration}) [#{@lyrics}]"
  end
end
aSong = KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s » "KS: My Way--Sinatra (225) [And now, the...]"

Örnek değişkenlerimizden olan @lyrics'in değerini doğru olarak gösterebiliyoruz. Bunu yapmakla, alt sınıfın atalarının örnek değişkenlerine direkt olarak ulaşmasını sağladık. Peki öyleyse, to_s metodunu bu şekilde gerçekleştirmek neden kötü bir yoldur?

Bunun cevabı, iyi programlama stiliyle ilgilidir (decoupling diye de adlandırılır). Örneğin, Song'u şarkı sürelerini milisaniyeler şeklinde gösterecek şekilde değiştirdiğimizi düşünelim. Aniden KaraokeSong gülünç değerler vermeye başlayacaktır. ``My Way''in karaoke versiyonunun 3750 dakika sürmesi düşüncesi bile yeterince ürkütücü.

Bu problemi, her sınıfın kendi iç durumuna sahip olmasıyla çözüyoruz. KaraokeSong#to_s çağırıldığı zaman şarkı detayları için onun ebeveyni olan Song sınıfının to_s metoduna başvuruyoruz. Böylece şarkı sözü bilgisine, şarkının detaylarını da ekleyerek bize sonucu geri döndürüyor. Buradaki küçük hilemiz, Ruby'nin ``super'' anahtar kelimesi olacaktır. super'i argüman olmaksızın çağırırsanız, Ruby nesnenin ebeveynine bir mesaj gönderir ve o anki metoda geçilmiş olan parametreleri de geçerek, metotla aynı isimde olan bir metot çağırır. Şimdi yeni ve geliştirilmiş to_s metodumuzu gerçekleştirebiliriz.

class KaraokeSong < Song
  # Format ourselves as a string by appending
  # our lyrics to our parent's #to_s value.
  def to_s
    super + " [#{@lyrics}]"
  end
end
aSong = KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s » "Song: My Way--Sinatra (225) [And now, the...]"

Ruby'ye KaraokeSong'un Song sınıfının bir alt sınıfı olduğunu söyledik ancak Song için bir ebeveyn belirlemedik. Eğer bir sınıf tanımlarken, bir ebeneyn belirtmezsek, Ruby varsayılan olarak Object sınıfını atar. Bu da tüm nesnelerin ata olarak Object sınıfına sahip olduğu ve bu sınıfın tüm örnek metotlarının Ruby'deki her nesne için geçerli olduğu anlamına gelir. Sayfa 18'de to_s metodunun tüm nesneler için geçerli olduğundan bahsetmiştik. Niçin bilinmez, to_s metodu Object sınıfındaki 35'i aşkın metottan biridir. Tam liste, sayda 351'den itibaren belirtilmiştir.

Inheritance and Mixins

Bazı nesneye yönelik diller (C++ gibi), bir sınıfın birden fazla ebeveyne sahip olmasını sağlayan ve fonksiyonel olarak istediği sınıftan miras edinmesine izin veren çoklu mirası destekler. Güçlü olmasına rağmen, bu teknik miras hiyerarşisini belirsizleştirdiği için tehlikeli olabilir.

Diğer diller, Java gibi, tekli mirası destekler. Bu durumda, bir sınıf yalnızca tek bir ebeveyne sahip olabilir. Daha açık (ve gerçekleştirmesi daha kolay) olsa da, tekli mirasın bazı sakıncaları vardır---gerçek dünyada nesneler özelliklerini çoğu zaman çoklu kaynaklardan miras alırlar (örneğin bir top hem zıplayan hem de küresel birşeydir.)

Ruby, tekli mirasın basitliği ile çoklu mirasın gücünü birleştirerek ilginç ve güçlü bir uzlaşma önerir. Bir Ruby sınıfı, doğrudan yalnızca tek bir ebeveyne sahip olabilir. Ancak, bir Ruby sınıfı istediği sayıda mixin'in (bir mixin, bir sınıf tanımlamasının bir parçası gibidir) fonksiyonalitesine sahip olabilir. Bu size hiçbir sakınca olmaksızın, kontrollu bir çoklu miras benzeri bir yetenek sağlar. Sayfa 98'den itibaren mixin'leri daha ayrıntılı incelemeye başlayacağız.

Bu bölümün ilerisinde sınıflara ve onların metotlarına bakacağız. Şimdi, Song sınıfının örnekleri gibi nesnelerle uğraşalım.

Objects and Attributes

Daha önce oluşturduğumuz Song nesneleri iç durumlar içeriyordu (şarkı ismi ve yorumcusu gibi). Bu durumlar, bu nesnelere özgüydü---yani başka hiçbir nesne bu nesnelerin örnek değişkenlerine erişemezdi. Genel olarak bakıldığında, bu İyi Birşeydir. Bu, bir nesnenin sadece kendi tutarlılığından sorumlu olması anlamına gelir.

Ancak, tamamen ketum bir nesne, oldukça kullanışsızdır---bir defa onu yaratırsınız, ancak daha sonra hiçbirşey yapamazsınız. Normal olarak, bir nesnenin durumuna erişebileceğiniz ve yönlendirebileceğiniz metotlar tanımlarsınız. Nesnenin bu görülebilen harici yönlerine nitelikleri denir.

Song nesnelerimiz için ihtiyacımız olan ilk şey, şarkının adını, yorumcusunu (böylece şarkı çalarken gösterebiliriz) ve süresini (ilerleme çubuğu gibi birşey gösterebiliriz) bulabilmektir.

class Song
  def name
    @name
  end
  def artist
    @artist
  end
  def duration
    @duration
  end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.artist » "Fleck"
aSong.name » "Bicylops"
aSong.duration » 260

Burada, üç örnek niteliğin değerlerini döndüren üç erişgeç (accessor) metodu tanımlıyoruz. Ruby bu iş için uygun bir kısayol sağlar: attr_reader bu erişgeç metotlarını sizin için sağlar.

class Song
  attr_reader :name, :artist, :duration
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.artist » "Fleck"
aSong.name » "Bicylops"
aSong.duration » 260

Bu örnekle yeni birşeylerle tanıştık. :artist construct'ı, artist'e karşılık gelen bir Symbol nesnesine döndüren bir ifadedir. :artist'i, değişken artist'in bir adı olarak düşünürken, artist'i de değişkenin değeri olarak düşünebilirsiniz. Bu örneğimizde, erişgeç metotlarımızı name, artist ve duration olarak adlandırdık. Bunlara karşılık gelen örnek değişkenler; @name, @artist ve @duration otomatik olarak yaratılırlar. Bu erişgeç metotları, önceden elle yazdıklarımızla tıpatıp aynıdır.

Writable Attributes

Bazen bir nesnenin dışından bir nitelik eklemeniz gerekebilir. Örneğin, bir şarkının uzunluğunun tahminen olduğunu varsayalım (muhtemelen bir CD ya da MP3'deki bilgiden alınmış). Şarkıyı ilk kez çaldığımızda, süresinin gerçekten ne kadar olduğunu buluyoruz ve bu yeni değeri Song nesnemizin içine yerleştiriyoruz.

C++ ya da Java gibi dillerde, bu işi kurucu fonksiyonlarla yaparsınız.

class JavaSong {                     // Java code
  private Duration myDuration;
  public void setDuration(Duration newDuration) {
    myDuration = newDuration;
  }
}
s = new Song(....)
s.setDuration(length)

Ruby'de bir nesnenin nitelikleri, herhangi bir değişkenmiş gibi erişilebilir. Bunu daha önce aSong.name'deki gibi gördük. Öyleyse, bir nitaliğe yeni bir değer vermek istediğinizde bu değişkenlere atamak çok doğal bir iş olacaktır. En Az Sürpriz Prensibi'ne bağlı kalarak, Ruby'de yaptığınız herşey budur.

class Song
  def duration=(newDuration)
    @duration = newDuration
  end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.duration » 260
aSong.duration = 257   # set attribute with updated value
aSong.duration » 257

``aSong.duration = 257'' ataması, 257'yi argüman olarak geçerek aSong nesnesinin duration= metodunu çağırır. Aslında bir metot ismini sonunda eşittir işaretiyle tanımlamak, bu ismi, atamanın sol-el tarafında görülmesini uygun kılar.

Tekrar belirtelim, Ruby bu basit nitelik kurma metotları için bir kısayol sağlar.

class Song
  attr_writer :duration
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.duration = 257

Virtual Attributes

Bu nitelik erişim metotları, yalnızca bir nesnenin örnek değişkenleri için bir ambalaj olmak zorunda değildir. Örneğin, bir şarkının süresine saniye bazında değil de dakika ve kesirleri bazında ulaşmak istiyoruz.

class Song
  def durationInMinutes
    @duration/60.0   # force floating point
  end
  def durationInMinutes=(value)
    @duration = (value*60).to_i
  end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.durationInMinutes » 4.333333333
aSong.durationInMinutes = 4.2
aSong.duration » 252

Burada, bir sanal örnek değişken yaratmak için nitelik metotlarını kullanıyoruz. Dışardan bakılınca, durationInMinustes herhangi bir nitelik gibi gözüküyor. Ancak, aslında örnek değişkene tekabül eden hiçbir şey yok.

Burada meraktan daha fazlası yatar. Bertrand Meyer, nirengi noktası olan kitabı Object-Oriented Software Construction 'nda bunu Değişmeyen Erişim Prensibi olarak tanımlar. Örnek değişkenlerle hesaplanmış değerler arasındaki farkı gizlemekle, sınıfınızın gerçekleştirimini dış dünyanın geri kalanından korumuş olursunuz. Gelecekte; sınıfınızı kullanan milyonlarca satır kodu etkilemeden istediğiniz şeylerin nasıl çalıştığını değiştirmekte serbestsiniz. Bu büyük bir kazançtır.

Class Variables and Class Methods

Şimdiye kadar, yarattığımız bütün sınıflar örnek değişkenler ve örnek metotlar içeriyordu: sınıfın belli bir kısım örneğiyle ilişkilendirilmiş değişkenler ve bu değişkenler üzerinde çalışan metotlar. Bazen, sınıflar kendi durumlarına sahip olmak zorunda olabilirler. İşte bu noktada, sınıf değişkenleri devreye girer.

Class Variables

Bir sınıf metodu, bir sınıfın tüm nesneleri ve daha sonra göreceğimiz sınıf metotları tarafından erişilebilirdir. Verilmiş bir sınıfın belirli bir sınıf değişkeninin yalnızca tek bir kopyası bulunur. Sınıf değişkenlerinin isimleri, ``@@count'' gibi iki ``salyangoz'' işaretiyle başlar. Global ve örnek değişkenlerin tersine, sınıf değişkenleri kullanılmadan önce başlangıç değeri verilmelidir. Genellikle bu hazırlama, sınıf tanımlaması içinde basit bir atamayla yapılır.

Örneğin, müzik kutumuz her bir şarkının kaç kere çalındığını kaydetmek istiyor. Bu sayaç muhtemelen Song nesnesinin bir örnek değişkeni olabilir. Şarkı çalındığı zaman, örnek değişkenimiz artar. Ancak aynı zamanda toplamda ne kadar şarkının da çaldığını bilmek istiyoruz. Bu işi, tüm Song nesnelerini taratıp sayaçlarını ekleyerek ya da Tanrı'nın Kilisesi Planı'na göre bir risk alıp global bir değişken kullanarak halledebiliriz. Ya da, bir sınıf değişkeni kullanabiliriz.

class Song
  @@plays = 0
  def initialize(name, artist, duration)
    @name     = name
    @artist   = artist
    @duration = duration
    @plays    = 0
  end
  def play
    @plays += 1
    @@plays += 1
    "This  song: #@plays plays. Total #@@plays plays."
  end
end

Hata ayıklama amaçları için, Song#play'in şarkının kaç kere çaldığı bilgisini ve toplamda tüm şarkıların çalma sayısını tutan bir dizgiyi döndürecek biçimde düzenledik. Bunu kolayca test edebiliriz.

s1 = Song.new("Song1", "Artist1", 234)  # test songs..
s2 = Song.new("Song2", "Artist2", 345)
s1.play » "This  song: 1 plays. Total 1 plays."
s2.play » "This  song: 1 plays. Total 2 plays."
s1.play » "This  song: 2 plays. Total 3 plays."
s1.play » "This  song: 3 plays. Total 4 plays."

Sınıf değişkenleri bir sınıfa ve onların örneklerine özeldir. Eğer onların dış dünya tarafından erişilebilir olmalarını istiyorsanız bunun için bir erişgeç metodu yazmalısınız. Bu bir örnek metot olabilir, ya da önümüzdeki bölümde göreceğimiz gibi bir sınıf metodu olabilir.

Class Methods

Bazen bir sınıf, belirli bir nesneye bağlı olmaksızın metotlara ihtiyaç duyabilir.

Daha önce böyle bir metotla karşılaşmıştık. new metodu, kendisi herhangi bir şarkıyla ilişkilendirilmediği halde bir Song nesnesi yaratıyordu.

aSong = Song.new(....)

Sınıf metotlarının Ruby kütüphanelerine serpiştirilmiş olduğunu göreceksiniz. Örneğin, File sınıfının nesneleri belli başlı dosya sistemindeki açık dosyaları temsil eder. Ancak, File sınıfı aynı zamanda açık olmayan dosyalar için ya da bir File nesnesine sahip olmayanlar için birçok metot sağlamaktadır. Eğer bir dosyayı silmek istiyorsanız, File.delete sınıf metodunu çağırırsınız.

File.delete("doomedFile")

Sınıf metotları, örnek metotlardan tanımlamalarıyla ayırd edilir. Sınıf metotları, sınıf ismini takiben metodun önüne bir periyod konarak tanımlanır.

class Example

  def instMeth              # instance method   end

  def Example.classMeth     # class method   end

end

Müzik kutumuz çalınan dakika başına değil, her şarkı başına ücret alır. Böylece kısa şarkılardan uzun olanlara oranla daha fazla kazanç sağlarız. Şarkı listemizde olabileceğinden daha uzun şarkıların çalmasına engel olmak isteyebilirsiniz. Bu yüzden belli bir şarkının limiti aşıp aşmadığını kontrol eden SongList adında bir metot tanımlayabiliriz. Bu limiti, sınıf içinde başlangıç değeri verdiğimiz basit bir sabit (sabitleri hatırlayın? büyük harfle başlarlar) bir sınıf sabitiyle koyuyoruz.

class SongList
  MaxTime = 5*60           #  5 minutes
  def SongList.isTooLong(aSong)
    return aSong.duration > MaxTime
  end
end
song1 = Song.new("Bicylops", "Fleck", 260)
SongList.isTooLong(song1) » false
song2 = Song.new("The Calling", "Santana", 468)
SongList.isTooLong(song2) » true

Singletons and Other Constructors

Bazen, Ruby'nin nesneleri yaratırken izlediği varsayılan yolu geçersiz kılmak isteyebilirsiniz. Örneğin, bizim müzik kutumuza göz atalım. Birçok müzik kutumuz olduğu için, yurt geneline dağıtılmış müzik kutularımızın bakımını olabildiğince hızlı yapmak istiyoruz. Bu iş için yapmamız gerekenlerden biri de bir müzik kutusuna olan herşeyin bir kayıdını tutmaktır: çalınan şarkılar, alınan paralar, içine dökülen akışkan maddeler vb. gibi. Müzik için ağ bantgenişliği ayırmak istediğimizden, bu kayıt dosyalarını yerel olarak yerleştireceğiz. Bu da kayıtları tutmamıza yardımcı olacak bir sınıfa ihtiyacımız var anlamına geliyor. Ancak, her müzik kutusu başına sadece bir kayıt nesnesi olmasını ve bu nesnenin diğer tüm nesnelerin kullanması için paylaşılabilir olmasını istiyoruz.

Enter the Singleton pattern, documented in Design Patterns . Birşeyleri düzenleyeceğimiz için, bir kayit nesnesi yaratmak için tek yok, Logger.create'i çağırmaktır, böylece sadece bir adet kayıt nesnesi oluşturulduğundan emin oluruz.

class Logger
  private_class_method :new
  @@logger = nil
  def Logger.create
    @@logger = new unless @@logger
    @@logger
  end
end

Logger'ın new metodunu özel (private) yaparak, geleneksel constructor kullanan kullanarak bir kayıt nesnesi yaratmaya çalışan herşeyin önüne geçmiş oluruz. Yerine, Logger.create adında bir sınıf metodu sağlıyoruz. Bu metot, her çağırıldığında bu örneği döndüren, kayıtın bir örneğinin bir referansını tutan @@logger sınıf değişkenini kullanır. [Burada gerçekleştirdiğimiz tekiller (singleton) iş parçacığı (thread)korumalı değildir; eğer çoklu İş parçacıkları koşuyorsa, çoklu kayıt nesneleri tanımlamak daha uygun olacaktır. İş parçacıklarının güvenliğini kendimiz sağlamak yerine, sayfa 468'den itibaren bahsettiğimiz Ruby'nin Singleton mixin'ini kullanabilirsiniz.]

Logger.create.id » 537766930
Logger.create.id » 537766930

Sınıf metotlarını sözde-constructor'lar olarak kullanmak, sınıfınızı kullananlar için de hayatı kolaylaştıran bir durum olacaktır. Önemsiz bir örnek olarak, düzenli bir çokgeni temsil eden Shape sınıfı üzerinde duralım. Shape'in örnekleri, kenar sayısı ve çevresini constructor'a vermek suretiyle yaratılırlar.

class Shape
  def initialize(numSides, perimeter)
    # ...
  end
end

Ancak, birkaç yıl önceye kadar, bu sınıf programcılar tarafından, şekilleri, çevre olmaksızın; sadece isim ve kenar uzunlukları yardımıyla yaratmak için kullanılıyordu. Basitçe Shape'e birkaç sınıf metodu ekleyelim.

class Shape
  def Shape.triangle(sideLength)
    Shape.new(3, sideLength*3)
  end
  def Shape.square(sideLength)
    Shape.new(4, sideLength*4)
  end
end

Sınıf metotlarının birçok ilginç ve güçlü kullanım yolları vardır, ancak bunların hepsini incelemek bizim müzik kutumuzu daha erken bitirmemizi sağlamayacak; o yüzden devam edelim.

Access Control

Bir sınıf arayüzü hazırlarken, sınıfınızın dış dünyaya kaç erişim ortaya çıkaracağını düşünmek önemlidir. Sınıfınıza çok fazla erişime izin vermekle uygulamanızdaki artışı riske atmış olursunuz---sınıfınızın kullanıcılarını mantıksal arayüz yerine, önemsiz detaylar üzerinde düşünmeye zorlarsınız. İyi haber şu ki: Ruby'de bir nesnenin durumunu değiştirmek için tek yol metotlarından birini çağırmaktır. Kontrol metotlara erişir, ve siz de nesneye erişimi kontrol edersiniz. Bu işin iyi bir kuralı da, bir nesnenin durumunu geçersiz kılan hiçbir metodun var olamayacağıdır. Ruby'de üç çeşit koruma kademesi vardır.

``protected'' ve `private'' arasındaki fark göze fazla çarpmayan ve diğer nenseye yönelik dillere göre daha farklı olan bir farktır. Eğer bir metot korumalı (protected) ise, tanımlanan sınıfın ya da alt sınıflarının her örneği tarafından çağırılabilir. Eğer bir metot özelse (private) sadece çağıran nesnenin bağlamı içinde çağırılabilir---asla başka bir nesnenin özel metoduna doğrudan erişemezsiniz, nesne çağıran nesneyle aynı sınıfa sahip olsa bile.

Ruby, diğer nesneye yönelik dillerden başka önemli bir noktada daha ayrılır. Erişim kontolü, statik olarak değil, program çalışırken dinamik olarak belirlenir. Sadece kısıtlanan bir metot çalıştırılmak için atandığı zaman bir erişim denemesiyle karşılacaksınız.

Specifying Access Control

Metotlar için erişim kademelerini sınıfın içinden ya da public,protected ve private fonksiyonlarından birini kullanarak modül tanımlamaları yardımıyla yapabilirsiniz. Her bir fonksiyon iki farklı amaç için kullanılabilir.

Eğer hiç argüman olmadan kullanılırsa, üç fonksiyon da daha sonra tanımlanan metotlara varsayılan erişim kontrolünü atar. Eğer bir C++ ya da Java programcısıysanız, aynı etkiyi gerçekleştirmek için public kullanmanız size tanıdık gelecektir.

class MyClass

      def method1    # default is 'public'         #...       end

  protected          # subsequent methods will be 'protected'

      def method2    # will be 'protected'         #...       end

  private            # subsequent methods will be 'private'

      def method3    # will be 'private'         #...       end

  public             # subsequent methods will be 'public'

      def method4    # and this will be 'public'         #...       end end

Alternatif olarak, erişim kontrol fonksiyonlarına bir argümanmış gibi, metot isimlerini listeleyerek erişim kademelerini belirleyebilirsiniz.

class MyClass

  def method1   end

  # ... and so on

  public    :method1, :method4   protected :method2   private   :method3 end

Bir sınıfın initialize metodu otomatik olarak private olarak tanımlanmıştır.

Şimdi bir kaç örnek görme zamanı. Örneğin, hesaptan düşen paraları kredi olarak sayan bir muhasebe sistemi tasarladığımızı düşünelim. Bu kuralı kimsenin bozmasını istemediğimiz için, para üstlerini ve kredileri private olarak tanımladık ve artık işlem terimleri dahilinde dış arayüzümüzü tasarlayacağız.

class Accounts

  private

    def debit(account, amount)       account.balance -= amount     end     def credit(account, amount)       account.balance += amount     end

  public

    #...     def transferToSavings(amount)       debit(@checking, amount)       credit(@savings, amount)     end     #... end

Korumalı (protected) erişim, aynı sınıfın diğer nesnelerinin iç durumuna erişmek için kullanılır. Örneğin, bireysel Account'a, yeni bakiyelerini karşılaştırmak için izin vermek isteyebiliriz, ancak bu bakiyeleri dünyanın geri kalanından da saklamak istiyoruz (muhtemelen bunları başka bir formda sunmak istediğimiz için).

class Account
  attr_reader :balance       # accessor method 'balance'

  protected :balance         # and make it protected

  def greaterBalanceThan(other)     return @balance > other.balance   end end

balance niteliği korumalı olduğu için, yalnızca Account nesneleri içinden erişilebilir.

Variables

Şimdi, tüm bu nesneleri yaratmak için bir karmaşaya sürüklenmeyelim, bu yüzden hiçbirini kaybetmeyeceğimizden emin olalım. Değişkenleri nesnelerin izlerini tutmak için kullandık; her değişken bir nesneye referans içeriyor.

Figure not available...

Bu durumu birkaç kodla doğrulayalım.

person = "Tim"
person.id » 537771100
person.type » String
person » "Tim"

İlk satırda, Ruby, ``Tim'' değişkeni içinde yeni bir String nesnesi yaratır. Bu nesneye olan referans yerel değişken person içinde tutulur. Hızlı bir kontrolle, bir nesne id'si, bir tip ve bir değerle birlikte, gerçekten bir dizginin karakteristiğini aldığını görüyoruz.

Peki, bir değişken bir nesne midir?

Ruby'de cevap ``hayır'' olacaktır. Bir değişken, basitçe bir nesneye bir referanstır. Nesneler bir yerlerdeki (çoğu zaman yığında (heap) ) büyük bir havuzda yüzerler ve değişkenler tarafından işaret edilirler.

Şimdi örneğimizi biraz daha karışık hale getirelim.

person1 = "Tim"
person2 = person1
person1[0] = 'J'
person1 » "Jim"
person2 » "Jim"

Burada ne oldu? person1'in ilk karakterini değiştirdik, ancak hem person1 hem de person2, ``Tim'''den ``Jim'''e dönüştü.

Bu durum, bize değişkenlerin nesnelerin kendilerini değil, referanslarını tuttukları gerçeğine geri götürdü. person1'den person2 atamasıyla yeni bir nesne yaratılmaz; basitçe person1'in nesne referansı person2'ninkine kopyalanır, böylece hem person1 hem de person2 aynı nesneyi referans ederler. Bu durumu, sayfa 31'de Şekil 1.3'de gösterdik.

Atama, nesnelere takma ad vermek gibidir, potansiyel olarak, aynı nesneye birden fazla değişkenle referans edebilirsiniz. Ancak bu durum kodunuzda problemlere yol açmaz mı? Evet, bazı problemler doğurabilir ancak tahmin ettiğiniz kadar sık değil (örneğin Java'daki nesneler aynı yolla çalışırlar). Örnek olarak, 3.1'deki örnekte, aynı içerikte yeni bir String nesnesi yaratmaya yarayan String'in dup metodunu kullanarak, takma isim vermeyi engellemiş oluyorsunuz.

person1 = "Tim"
person2 = person1.dup
person1[0] = "J"
person1 » "Jim"
person2 » "Tim"

Ayrıca, hernagi birinin belirli bir nesneyi dondurmak suretiyle (dondurulmuş nesneler hakkında sayfa 251'de ayrıntılı olarak bahsedeceğiz) değiştirmesini engellemiş olursunuz. Dondurulmuş bir nesneyi değiştirmeye teşebbüs etmek, Ruby'nin bir TypeError istisnası vermesine neden olacaktır.

person1 = "Tim"
person2 = person1
person1.freeze       # prevent modifications to the object
person2[0] = "J"
produces:
prog.rb:4:in `=': can't modify frozen string (TypeError)
	from prog.rb:4


Previous < Contents ^
Next >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.