Skip to content

Class (Sınıf)

Ruby, Object-Oriented (OO) bir dil olduğu için, methodları değişkenleri ve benzeri şeyleri içinde barından bir tür taşıyıcıya ihtiyaç duyar. İşte bu taşıyıcıya Class ya da sınıf diyoruz. Aslında ben tür demeyi tercih ediyorum sınıf yerine.

Zaten önceki konularda Class Methods, Instance Methods gibi kavramlara girmiştik.

Class’lar birbirinden türeyebilir (Hani class.superclass şeklinde analizler yapmıştık)

Teknik olarak bir dosyada birden fazla Class tanımlaması yapılabilir. Örneğin, my_class.rb adlı bir dosya içinde farklı farklı Class tanımlamaları olabilir;

class MyClass
end

class OtherClass
end

a = MyClass.new    # => #<MyClass:0x007ffa2b09b758>
b = OtherClass.new # => #<OtherClass:0x007ffa2b09b3c0>

Class tek başına bir nesne (Obje) bu bakımdan instantiate etmeseniz bile (Yani a = Array.new gibi) kullanabileceğiniz bir şeydir.

Class’ların diğer bir özelliği de açık olmasıdır. Ruby’nin bence en harika özelliği, built-in yani dilin çekirdeğinden gelen Class’lara bile method / property eklemeniz mümkündür. Bu konuları Monkey Patching kısmında detaylı göreceğiz.

En basit tanımıyla Class aşağıdaki gibidir:

class Merhaba
  def initialize(isim)
    @isim = isim
  end

  def selam_sana
    "Selam sana, #{@isim}"
  end
end

hey = Merhaba.new "Uğur"
hey.selam_sana # => "Selam sana, Uğur"

initialize methodu, Class’dan ilk örnek türedildiğinde (yani bir instance oluşturulduğunda) tetiklenir ve bir tür Class ile ilgili ön tanımlamaların yapıldığı alan konumundadır. Benzer dillerdeki class constructor’ı gibi düşünülebilir.

@isim ise Instance Variable yani Class’tan türeyen nesneye ait değişkendir.

selam_sana ise bu Class’ın bir method’udur. hey değişkeni, Merhaba Class’ından türemiş bir Instance’dır. Merhaba sınıfındaki tüm method’lar inherit yani miras olarak hey nesnesine geçmiştir.

Class’ı tanımlarken ister klasik ister block yöntemini kullanabilirsiniz. Klasik yöntem:

class Person
end

jack = Person.new # => #<Person:0x007fb0521a4820>

Block ise;

Person = Class.new do
end

jack = Person.new # => #<Person:0x007fc42c0c8648>

şeklindedir. Class isimleri Büyük harfle başlar.

Public Instance Method’ları

Bir Class’tan türeyen şeye Instance diyoruz. Ruby’de Class’lar first-class objects olarak geçer yani birinci sınıf nesnelerdir. Bu da şu anlama gelir, aslında her Class, Kernel’dan gelen Class nesnesinden türemiş alt sınıftır :)

allocate, new ve superclass

superclass ilgili Class’ın kimden geldiğini / türediğini gösterir. Benzer örnekleri kitabın başında yapmıştık:

String.superclass      # => Object
Object.superclass      # => BasicObject
BasicObject.superclass # => nil

new ise önce allocate method’unu çağırıp hafızada gereken yeri ayırır, yani instantiate edeceğimiz Class’ın sınıfını organize eder, sonra oluşacak Class’ın initialize method’unu çağırıp varsa ilgili argümanları pas eder. Her .new çağırıldığında bu iş olur.

Private Instance Method’ları

inherited(subclass)

İlgili sınıfın alt sınıfı oluşturulduğunda tetiklenir.

class Animal
  def self.inherited(subclass)
    puts "Yeni subclass: #{subclass}"
  end
end

class Cat < Animal
end

class Tiger < Cat
end

# Yeni subclass: Cat
# Yeni subclass: Tiger

Animal (hayvan) sınıfından, Cat (kedi) ürettik, Tiger (kaplan)’ı da yine Cat (kedi)’den ürettik...

Accessors (getter + setter)

Özel method’lar kullanarak Meta Programming mantığıyla Ruby, Instance Variable’ları yönetmeyi kolaylaştırır. Yukarıdaki Merhaba Class’ındaki @isim için aslında getve set yani oku ve yaz method’ları tanımlamamız lazım ki ilgili değişken üzerinde işlem yapabilelim. Önce uzun yolu, sonra doğru ve kısa yolu görelim:

class Person
  def name
    @name
  end

  def name=(name)
    @name = name
  end
end

vigo = Person.new  # => #<Person:0x007f903b8d0590>
vigo.name          # => nil
vigo.name = "Uğur" # => "Uğur"
vigo.name          # => "Uğur"

name method’unu çağırınca bize instance variable olan @name dönüyor. İlk anda set etmediğimiz yani değer atamadığımız için nil geliyor. Daha sonra vigo.name = "Uğur" diyerek atama yapıyoruz ve artık değerini belirlemiş oluyoruz. Bu iş için 2 tane method yazdık. name ve name= method’ları.

İşte bu noktada accessors imdadımıza yetişiyor:

class Person
  attr_accessor :name
end

vigo = Person.new  # => #<Person:0x007fb7c9a4c620>
vigo.name          # => nil
vigo.name = "Uğur" # => "Uğur"
vigo.name          # => "Uğur"

attr_accessor :name dediğimizde, Ruby, bizim için name ve name= method’ları oluşturuyor. Keza sadece bununla kalmayıp, pek çok farklı kullanım imkanları sunuyor.

attr modülüyle:

  • attr
  • attr_accessor
  • attr_reader
  • attr_writer

gibi özel getter/setter’lar geliyor. Yukarıdaki örneği attr ile yapalım;

class Person
  attr :name, true
end

Person.instance_methods - Object.instance_methods # => [:name, :name=]

Otomatik olarak 2 method ekledi : [:name, :name=]. Aynı şeyi attr_accessor :name ile de yapabilirdik:

class Person
  attr_accessor :name
end

Person.instance_methods - Object.instance_methods # => [:name, :name=]

Eğer sadece attr_reader kullansaydık, sadece ilgili instance variable’ını okuyabilir ama değerini set edemezdik!

class Person
  attr_reader :name
end

Person.instance_methods - Object.instance_methods # => [:name]

vigo = Person.new
vigo.name # => nil
vigo.name = "Uğur" # => NoMethodError: undefined method `name=’ for #<Person:0x007ffe4d0e8528>

Gördüğünüz gibi NoMethodError hatası aldık çünki setter yani name= method’u oluşmadı! Peki sadece attr_writer olsaydı?

class Person
  attr_writer :name
end

Person.instance_methods - Object.instance_methods # => [:name=]

vigo = Person.new
vigo.name = "Uğur" # => "Uğur"
vigo.name # => NoMethodError: undefined method ‘name’ for #<Person:0x007fb92b9d45b8 @name="Uğur">

Set edebiliyoruz ama get edemiyoruz! Peki attr_writer nerede işimize yarar? Örneğin sadece Class’ı initialize ederken değer pas edip sınıf içinde bir değişkene atama yapmak gerektiğinde kullanabilirsiniz:

class Person
  attr_writer :name

  def initialize(name)
    @name = name
  end

  def greet
    "Hello #{@name}"
  end
end

vigo = Person.new "Uğur"
vigo.greet # => "Hello Uğur"

@name değişkenini sadece ilk tetiklenmede set ediceksem ve dışarıdan okuma ihtiyacım yoksa bu şekilde kullanabilirim!

Class Variables

@@ ile başlayan değişkenler Class Variable (Sınıf Değişkeni) olarak tanımlanır. Yani Ana Class’a ait bir değişkendir. Her yeni instance oluştuğunda bu değer ait olduğu üst sınıftan erişilebilir:

class Person
  attr_accessor :name

  @@amount = 0
  def initialize(name)
    @@amount += 1
    @name = name
  end

  def greet
    "Hello #{name}"
  end

  def how_many_people_created
    "Number of people: #{@@amount}"
  end
end

user1 = Person.new "Uğur"
user2 = Person.new "Yeşim"
user3 = Person.new "Ezel"

Person.class_variable_get(:@@amount) # => 3
user3.how_many_people_created        # => "Number of people: 3"

Class Methods

İlgili Class’dan türetme yapmadan, direk Class’dan çağırılan özel method’dur. Bu method’u çağırmak için sınıftan herhangi bir türetme yapmaya gerek olmaz, direkt olarak sınıf’tan çağırılır:

class Person
  attr_accessor :name

  @@amount = 0
  def initialize(name)
    @@amount += 1
    @name = name
  end

  def greet
    "Hello #{name}"
  end

  def how_many_people_created
    "Number of people: #{@@amount}"
  end

  def self.how_many_people_created
    "We have #{@@amount} copie(s)"
  end
end

user1 = Person.new "Uğur"
user2 = Person.new "Yeşim"
user3 = Person.new "Ezel"

Person.how_many_people_created # => "We have 3 copie(s)"

Person.how_many_people_created direkt olarak çağırılır!

Singletons

Sınıf içinde class komutunu kullanarak method oluşturmak içindir. Buna Singleton denir. Sadece bir kere instantiate (tetiklenme diyelim) olur. Örneğin alan hesabı yapacak bir sınıf düşünüyoruz ve bunun calculate method’u olsun. En x Boy bize metrekare’yi versin:

class Area
 class << self
   def calculate(width, height)
     width * height
   end
 end
end

Area.calculate(5, 5) # => 25

Gördüğünüz gibi hiçbir şekilde new ya da benzer bir şey türetme kullanmadık direkt olarak Area.calculate(5, 5) şeklinde kullandık. Keza aynı işi;

class Area
end

x = Area.new
def x.calculate(width, height)
  width * height
end
x.calculate 5,5 # => 25

şeklinde de yapabilirdik.

Inheritance (Miras)

Aslında bu da bildiğimiz bir şey. Sınıftan türeme yaparkan, türettiğimiz sınıfın özellikleri türeyene miras geçer.

class Animal
  attr_accessor :name, :kind

  def initialize(name)
    @name = name
  end

  def say_hi
    "Hello! I’m a #{@kind}, my name is #{@name}"
  end
end

class Cat < Animal
end

class Horse < Animal
end

bidik = Cat.new "Bıdık"
bidik.kind = "cat"

zuzu = Horse.new "Zuzu"
zuzu.kind = "horse"

bidik.say_hi # => "Hello! I’m a cat, my name is Bıdık"
zuzu.say_hi  # => "Hello! I’m a horse, my name is Zuzu"

Cat ve Horse Animal sınıfından < yöntemiyle türedi ve Animal deki tüm method’lar Cat ve Horse’a geçti.

Access Level (Erişim): Public, Private, ve Protected Method’lar

Class içindeki method’lar duruma göre erişilebilirlik açısından kısıtlanabilir. public olanlar her yerden erişilebilirken (bu default bir durumdur), private olana sadece içeriden erişilebilir, protected olana ise ancak alt sınıftan türeyenden erişilebilir.

class User
  def bu_sayede_private_cagirabilirim
    bu_sadece_iceriden
  end

  private
  def bu_sadece_iceriden
    puts "Bu private method. Bu method instance’dan çağırılamaz!"
  end

  protected
  def bu_sadece_subclass_veya_instance_dan
    puts "Bu proteced method."
  end
end

u = User.new
u.bu_sadece_iceriden # => NoMethodError: private method ‘bu_sadece_iceriden’ called for #<User:0x007feb9d0d2560>

Gördüğünüz gibi bu_sadece_iceriden method’unu User dan instantiate ettiğimiz u üzeriden çağıramıyoruz. private olduğu için ancak içeriden çağırılabilir:

u.bu_sayede_private_cagirabilirim # => "Bu private method. Bu method instance’dan çağırılamaz!"

public olan bu_sayede_private_cagirabilirim method’u içeriden private method olan bu_sadece_iceriden ’e erişebildi. Peki ya protected ? Eğer direkt olarak çağırmaya kalksaydık:

u.bu_sadece_subclass_veya_instance_dan # => NoMethodError: protected method ‘bu_sadece_subclass_veya_instance_dan’ called for #<User:0x007fff131c60a0>

Hemen gerekeni yapalım; User Class’ından başka bir Class üretelim:

class SuperUser < User
  def initialize
    bu_sadece_subclass_veya_instance_dan
  end
end

y = SuperUser.new # => "Bu proteced method."

Method Aliasing

Bazı durumlarda, üst sınıftaki method’u ezmek gerekir. Bu işlemi yaparken aslında üst sınıftaki orijinal method’a da erişmeniz gerekebilir.

class User
  attr_accessor :name

  def initialize(name)
    @name = name
  end

  def give_random_age
    (20..45).to_a.sample
  end
end

class SuperUser < User
  alias :yedek :give_random_age # üst sınıftaki give_random_age’i sakladık, yedek adını verdik

  def give_random_age
    rnd = self.yedek
    "Kendi yaşım: 43, rnd= #{rnd}"
  end
end

u = User.new "vigo"
u.name            # => "vigo"
u.give_random_age # => 29

v = SuperUser.new "Uğur"
v.give_random_age # => "Kendi yaşım: 43, rnd= 44"

Örnekte, SuperUser Class’ında kafamıza göre give_random_age method’unu ezip kendi işlemimizi yaparken, üst sınıftan miras gelen orijinal method’u da yedekliyoruz, yedek adı altında.

Sınıflar Açıktır, Modifiye Edilebilir!

İster Kernel’dan ister başka bir yerden gelsin, her şekilde Class’lar modifiye edilebilir. Detayları Monkey Patching’de göreceğiz. Kısa bir örnek yapalım. String Class’ına neşemize göre bir method ekleyelim:

class String
  def hello
    "Hello: #{self}"
  end
end


"Deneme".hello # => "Hello: Deneme"

Tipi String olan her şeyin artık hello diye bir method’u oldu :)

Nested Class

Aynı Module’lerde olduğu gibi, iç içe Class tanımlamak da mümkündür. Kimi zaman düzenli olmak için (Namespace) kimi zaman da belli bir kuralı uygulamak için kullanılır;

class Animal
  attr_reader :name

  def initialize(name)
    @name = name
  end

  class Cat < Animal
  end

  class Horse < Animal
  end

  class Uber
  end
end

horse = Animal::Horse.new "Furry"
horse.name             # => "Furry"
horse.class            # => Animal::Horse
horse.class.superclass # => Animal

cat = Animal::Cat.new "Bıdık"
cat.name               # => "Bıdık"
cat.class              # => Animal::Cat
cat.class.superclass   # => Animal

alien = Animal::Uber.new
alien.respond_to?(:name) # => false
alien.class            # => Animal::Uber
alien.class.superclass # => Object

Cat ve Horse, Animal sınıfından türemiş, Uber ise sadece Animal namespace’i içinde olup kendi başına bir Class’ı temsil etmektedir.