- Bir noktanın hikayesi
- "Müşteri" istekleri
- "Özel" veri tipi?
- Dinamik bellek yönetimi
- Gerçekleme
- Yeni müşteri isteği
- Gözlemler
- Yapısal Programlama
- Nesne Yönelimli Programlama
- C++
- Nitelikler
- Veri sarmalama
- Getters
- Yapıcı
- Tam gerçekleme
- Ruby
- Nitelikler
- Getters
- Yapıcı
- Setters
- "Getters" tekrar
- attr_reader
- "Setter" tekrar
- attr_writer
- attr_accessor
- Dikkat!
- Veri sarmalama
- private
- public
- Veri Sarmalama
- Nitelik yazıcıları tekrar
- Kıssadan Hisse
- Noktanın yer değiştirmesi
- Kompozisyon
- Dikdörtgen tekrar
- Örnek: Dikdörtgenin SVG çizimi
- Delegasyon
- Forwardable modülü
- Katıştırma: extend
- Katıştırma: include
- Comparable modülü
- Karşılaştırma: <=>
- Veri sarmalama: protected
- eql?: Eşdeğer mi?
- Sınıf metotları
- Miras alma
- Miras alma mekaniği
- super
- Şecere (Soy ağacı)
- Katıştırma (mixin) tekrar
- Çoklu miras alma
- Dinamik miras alma
- İstisnalar
- Hata üret: raise
- Hata yakala: rescue
- begin / end
- Ruby hata hiyerarşisi
- İstisnaları sadece istisnai durumlarda kullanın
- Çok Biçimlilik
- Dinamik Dağıtım
- Arayüz
- Arayüz ve Çok Biçimlilik
- Nesne İlişkileri
- UML Diyagramları
- is-a
- Kompozisyon
- has-a
- Kompozisyon veya Miras Alma?
- Delegasyon
- Delegasyon - Forwardable modülü
- Örnek: Üretilen nesneleri sayı
- Sınıf değişkenleri
- "Class variables considered harmful"
- "Nesneye özgü"lük
- self'in öyküsü
- Sınıf nesnesi nitelikleri
Bir noktanın hikayesi
"Müşteri" istekleri
Koordinatları verilen iki noktayı görüntüle
"Ürün"ü önce C programlama dili ile gerçekleyeceğiz
- C bir Yapısal Programlama ("Structured Programming") dili
"Müşteri" kodu taslağı
#include <stdio.h>
#include <stdlib.h>
struct point {
...
}
...
int main(void)
{
struct point *p = point_create(3, 5);
struct point *q = point_create(9, 7);
point_print(p);
point_print(q);
point_destroy(p);
point_destroy(q);
return 0;
}
Nokta için bir struct
oluşturmaya karar verdik
struct
→ yapıBirbiriyle ilişkili verileri bir isim altında grupluyor
C gibi bir Yapısal Programlama dilinde özel veri tipleri kurgulamanın neredeyse tek yolu
"Özel" veri tipi?
Önce "özel" olmayan, ki ona "ilkel" ("primitive") veri tipi diyeceğiz, ne olduğunu anlayalım
int x;
Bakın burada
int
bir "ilkel" veri tipiSöz dizimine dikkat: önce veri tipi (
int
) sonra o veri tipinde olacak tanımlayıcı (x
)Bu bir değişken bildirimi ("variable declaration")
Birlikte tekrar edelim!
x
adıint
tipinde bir değişkeni tanımlar
C programlama dili kurallarına göre x
bir yerel değişkense ilk değeri belirsiz
int x = 3;
Bildirim yaparken değişkeni "ilkle"yebiliriz
Bu bir tür "inşa" ("construction")
x
adıyla erişilecek bir veriyi inşa (construct) ediyoruz
Şimdi "nokta"ya ("point") geçebiliriz
C'de veri tipleri?
int
,float
,double
,char
, ...Bunlar ilkel veri tipleri
C'de
point
adında bir veri tipi yokBu "özel" veri tipini biz tanımlayacağız ("definition")
struct point {
int x;
int y;
};
C söz dizimi kurallarına göre
point
veri tipini tanımladıkBu bir veri tipi tanımı, veri bildirimi değil
Gelin şimdi veri bildirimini yapalım
Aynı söz dizimi kuralını kullanacağız
Önce veri tipi sonra o veri tipinde olacak tanımlayıcı
struct point p;
Veri tipi?
struct point
Tanımlayıcı (değişken)?
p
int
'de olduğu gibi sadecestruct
diyemeyiz değil mi?int
tek,struct
ise ne tanımladığınıza bağlı olarak çokTanıma verdiğiniz adla (
point
) hangistruct
tanımı olduğunu belirtiyoruz
Peki bildirim sırasında ilkleme yapabilir miyiz? Tabii, şöyle:
struct point p = { 3, 5 };
veya daha güzeli (C99):
struct point p = {
.x = 3,
.y = 5
};
struct point p = { 3, 5 };
Bu bir statik yer ayırma ("allocation")
p
için bellekte gerekli yer derleme zamanında hesaplanıp program yüklendiğinde ayrılıyorBelirsiz sayıda
point
ile çalışacaksak ne yapmalıyız?Belirsizlik statik yöntem seçeneğini eliyor
Dinamik bellek yönetimi
struct point *p = (struct point *)malloc(sizeof(struct point));
Bir an için C'nin "aignment" ve "padding" kurallarını göz ardı edersek kabaca şöyle
Bir
point
'de ikiint
varBir
int
64 bitlik bir sistem 8 bayt ise bellekte 2x8 = 16 baytlık yer ayırdık*p
işaretçisi artık bize (programa) ait geçerli 16 baytlık bellek alanının başlangıç adresine işaret ediyor
Unutmayın C'de bir bellek alanına yazmak için öncelikle o alanın size ait olması gerekmiyor
Önünüze gelen yere ev inşa edemezsiniz
Önce arsanın size ait olması lazım
Arsanın tapusunu ilgili kişi ve kurumların koordinasyonuyla almalısınız
Belleği de işletim sisteminde talep ederek almalısınız, nasıl?
malloc
Arsanın bir bedeli var, ama bellek için bir bedel yok sadece dürüst vatandaşlık var
- Dürüst? İşiniz bittiğinde "arsayı", yani ayrılan belleği iade edin
struct point *p = (struct point *)malloc(sizeof(struct point));
...
free(p);
Gerçekleme
Şimdi bu "özel" veri tipini kullanarak gerçeklemeye geçeceğiz
#include <stdio.h>
#include <stdlib.h>
struct point {
int x;
int y;
}
...
int main(void)
{
struct point *p = point_create(3, 5);
struct point *q = point_create(9, 7);
point_print(p);
point_print(q);
point_destroy(p);
point_destroy(q);
return 0;
}
struct point {
int x;
int y;
};
struct point *point_create(int x, int y)
{
struct point *this = (struct point *)malloc(sizeof(struct point));
this->x = x;
this->y = y;
return this;
}
void point_destroy(struct point *this)
{
free(this);
}
void point_print(const struct point *this)
{
printf("(%d,%d)", this->x, this->y);
}
Bakın burada malloc
ve free
sırasıyla point_create
ve point_destroy
içinde
point_create
bir "yapıcı" fonksiyon → constructor (kısaca CTOR)point_destroy
bir "yıkıcı" fonksiyon → destructor (kısaca DTOR)CTOR ile bellekten yer alırken ilkleme işlemi de yapıyoruz (başka işlemler de yapılabilir)
DTOR ile belleği geri verirken varsa değişkene ait izleri de yok edebiliriz (örnekte buna gerek olmamakla beraber)
Yeni müşteri isteği
Verilen bir noktanın, verilen bir başka noktanın solunda olup olmadığını görüntüle
#include <stdio.h>
#include <stdlib.h>
struct point {
int x;
int y;
}
...
int main(void)
{
struct point *p = point_create(3, 5);
struct point *q = point_create(9, 7);
point_print(p), printf(" noktası ");
point_print(q), printf(" noktasının ");
printf("%s.\n", point_isleft(p, q) ? "solunda" : "sağında");
point_destroy(p);
point_destroy(q);
return 0;
}
int point_isleft(const struct point *this, const struct point *that)
{
return this->x < that->x;
}
int point_isright(const struct point *this, const struct point *that)
{
return this->x > that->x;
}
Gözlemler
Ne yaptık?
Yapısal bir Programlama diliyle "veri odaklı" bir programlama yaptık
Hikayenin merkezinde
struct point
veri tipi varBu veri tipinden türetilmiş veriler var ki bunların her birine "değişken" diyoruz (ör.
p
,q
)Nokta verileri üzerinde çalışan fonksiyonlar var:
point_create
,point_isleft
vb
Fonksiyonları birer emir gibi yorumlayabiliriz: create
, destroy
, print
, isleft?
Program belirli değişkenler üzerinde "emirler"le yürüyen bir eylemler dizisi gibi duruyor
Buna "Imperative Programming" deniliyor
Yapısal Programlama bir metodoloji
Problemin çözümünde kullanılan bir yöntem, bir perspektif
Bu metodolojide verinin pasif durumda bulunduğuna dikkat edin
Akış emirlerle yürüyor, denetim emri veren kişide
Ama yine disiplinli bir isimlendirmeyle "veri odaklılığını" koruduk
Fonksiyonlar daima
point_
ön ekiyle (prefix) isimlendirilmişCTOR haricinde her fonksiyonda ilk argüman eylemin baş aktörü olen bir nokta işaretçisi
Yapısal Programlama
Yapısal olmayan Programlama? İlk yaygın programlama dili: FORTRAN
İlk 5 çift sayıyı görüntüle
01 DO 20 i = 2, 10, 2 02 PRINT *, i 03 20 CONTINUE
Çıktı
2 4 6 8 10
03
satırında20
etiketi kullanılmış01
satırında kurulan döngüde, döngü sonu bu etikete verilen referansla belirtilmiş
Yapısal olmayan programlama dillerinde böyle bir sorun var(dı)
Çeşitli satırlara serpiştirilmiş doğrudan veya dolaylı atlamalar (GOTO)
Programın akışını takip etmek çok zor
Go To Statement Considered Harmful, Edgar Dijstra
Yapısal Programlama? (Tarihsel olarak) Önce Pascal, sonra C
for (int i = 2; i <= 10; i++)
printf("%d\n", i);
Bakın bu programda GOTO yok
Döngü sonu döngü yapısıyla yani "yapısal" olarak belirleniyor
Nesne Yönelimli Programlama
Yapısal Programlama, "Yapısal olmayan programlama"ya bir tür cevap
1980'li yılların başına gelindiğinde grafik kullanıcı arayüzleri (GUI) ortaya çıktı
GUI'lerde kullanıcı çeşitli "nesneler"le etkileşim halinde
Ör. "window", "button", "checkbox" vb
İşler "emirler/eylemler" yerine bu nesnelerle yürütülüyor
"Yapısal Programlama"dan daha farklı bir perspektif gerekiyor
C++
Şimdi ürün C++ programlama dilinde gerçekleyelim
C++ bir Nesne Yönelimli Programlama ("Object Oriented Programming") dili
Yine AT&T laboratuvarlarında icat edilmiş bir dil
C++ (en azından başlangıçta) C'ye yapılan NYP eklemeleriyle ortaya çıktı
Fakat ortaya çıkan dil C'den farklı (ve C'nin basit ruhuna aykırı)
++
son eki bu ekleme nosyonuna işaret ediyor
C++ > C değildir
C++ ≠ C (bu daha doğru)
#include <cstdio>
class Point {
...
};
...
int main(void)
{
Point p(3, 5), q(9, 7);
p.print(), printf(" noktası "), q.print(), printf(" noktasının ");
printf("%s.\n", p.isLeft(q) ? "solunda" : "sağında");
return 0;
}
struct
yerineclass
, işte bu önemlistruct point
tipi yerinePoint
Fonksiyonlarda "nesne nokta metot" notasyonu
class Point {
private:
int m_x;
int m_y;
public:
Point(int x = 0, int y = 0)
{
m_x = x;
m_y = y;
}
int x(void)
{
return m_x;
}
int y(void)
{
return m_y;
}
...
}
Nitelikler
Nitelikler (veya Özellikler): Attributes (veya Properties)
m_x
vem_y
Bu niteliklere doğrudan erişim yok
Yani nitelikler "sarmalanmış" (enkapsüle edilmiş)
Veri sarmalama
Veri Sarmalama: Data Encapsulation
public
veprivate
nitelendiricilerim_x
vem_y
niteliklerine erişimprivate
ile korunmuş
Getters
x
vey
public
metotlarıKorunmuş niteliklere bu metotlarla erişiyoruz
Yapıcı
Sınıf ile aynı isme sahip olması yönüyle diğer tüm metotlardan ayrılan bir metot var
Bu özel metot bir yapıcı metot (CTOR),
point_create
fonksiyonuna karşı geliyorArgümanlara öntanımlı değerler verebiliyorsunuz
Tam gerçekleme
class Point {
private:
int m_x;
int m_y;
public:
Point(int x = 0, int y = 0)
{
m_x = x;
m_y = y;
}
int x(void)
{
return m_x;
}
int y(void)
{
return m_y;
}
bool isLeft(Point other)
{
return m_x < other.x();
}
void print(void)
{
printf("(%d,%d)", m_x, m_y);
}
}
Ruby
Ve geldik Ruby'ye...
class Point
def initialize(x, y)
@x, @y = x, y
end
def x
@x
end
def y
@y
end
def isleft(other)
@x < other.x
end
def to_s
"(#{@x},#{@y})"
end
end
def main
p, q = Point.new(3, 5), Point.new(9, 7)
puts "#{p} noktası #{q} noktasının " + (p.isleft(q) ? 'solunda' : 'sağında')
end
main
Nitelikler
@x
ve@y
Bunlar doğal olrak korunmuş, ör.
p.@x
ile erişim yapamıyoruz
Getters
x
vey
metotları (aksi belirtilmediği sürece bunlar daimapublic
)@x
ve@y
niteliklerine bu metotlarla erişiyoruz
Yapıcı
initialize
metodu
Bir sınıf içinde varlığı mutlak şart değil
Ama nesneyi yaratıldıktan sonra ilklemek istiyorsak bu isimde bir metot yazıyoruz
Öyle ki bu metotun argümanları nesne
new
ile oluşturulurken verilen argümanlar
new
metodu
p = Point.new(3, 5)
Bakın burada
new
bir nesne üzerinden değil bir sınıf üzerinden çağrılıyorpoint_create
'in karşılığıYaptığı kabaca şu
Point sınıfında bir nesne için bellekte yer ayır (`alloc`) Sınıf içinde varsa ilklendirici metotu çağır (`initialize`) İlklendirilmiş nesneyi dön
initialize
metodunun dönüş değerleri anlamlı değil, çünkü dikkate alınmıyornew
metoduinitialize
ne dönerse dönsün bir nesne dönüyorinitialize
'ın biricik görevi nesneyi (ör. nesne niteliklerini) ilklemek
to_s
metodu
Tüm nesnelerde öntanımlı olarak bulunan bir metot,
point_print
karşılığıNesnenin metinsel temsili doğrudan veya dolaylı olarak istendiğinde kullanılıyor
Dolaylı?
p = Point.new(3, 5) puts p # metinsel temsili görüntüle puts "#{p} noktası" # veya böyle
Sınıf içinde
to_s
yoksa öntanımlı davranış?
Bakın burayı tekrar edelim
puts "#{nesne} falan"
ile
puts "#{nesne.to_s} falan"
Eşdeğerdir
Örneğimizde isleft
adını kullanmıştık, Ruby'de "yakışanı" left?
class Point
def initialize(x, y)
@x, @y = x, y
end
def x
@x
end
def y
@y
end
def left?(other)
@x < other.x
end
def to_s
"(#{@x},#{@y})"
end
end
def main
p, q = Point.new(3, 5), Point.new(9, 7)
puts "#{p} noktası #{q} noktasının " + (p.isleft(q) ? 'solunda' : 'sağında')
end
main
Hatta left?
yerine <
class Point
def <(other)
@x < other.x
end
end
Bu konuya daha sonra geleceğiz
Unutmayın: nesne.metot
veya nesne.metot?
veya nesne.metot!
çağrılarında geçen tüm metotlar tanımlı olmalı
class Sınıf
def metot
...
end
def metot?
...
end
def metot!
...
end
end
nesne = Sınıf.new
nesne.metot
nesne.metot?
nesne.metot!
Tekrar hatırlayalım:
Sonu
?
ile biten metotlar, metodun mantıksal bir değer (true
,false
) döndüğünü anlatırSonu
!
ile bitem metotlar, metodun dikkat edilmesi gereken bir "yan etki" ürettiğini, örneğin bir şeylerin üzerine yazıldığını anlatırBunlar birer "konvansiyon", sona bu karakterleri ekleyerek metoda sihirli bir ekleme yapılmasını sağlamıyorsunuz
"Sihri" yazan sizsiniz, sadece isimlendirme yoluyla niyetinizi daha açık ifade etmiş oluyorsunuz
Setters
x
niteliğini oku (Getter)
class Point
def initialize(x, y)
@x, @y = x, y
end
def x
@x
end
def y
@y
end
end
Niteliğe yazma nasıl yapılıyor?
initialize
metodunda görüyoruzNesne metotlarında niteliğe yazmak için basitçe
@x = «value»
yeterliAma bu işlem dışarı kapalı
x
metodu sadece okuma yapar
Bakın böyle:
class Point
def initialize(x, y)
@x, @y = x, y
end
def x
@x
end
def x=(value)
@x = value
end
def y
@y
end
def y=(value)
@y = value
end
end
Bu örnekte görülen x=
ve y=
metotları birer Setter
Yazma işlemini dışarı açan ("public" yapan) metotlar
Kısaca yazıcı, "writer" da denilebilir
p = Point.new(3, 5)
p.x = 7
p.y = 19
Aslında bu atamalar birer metot çağrısı
p = Point.new(3, 5)
p.x=(7)
p.y=(19)
Ruby'de metot adlarında sonda
=
varsa bu bir "mutation" semantiği taşırRuby'de metot çağrılarında parantezler genel olarak zorunlu değil (bu bir karışıklığa yol açmayacaksa)
Bir an için Ruby'nin sözdiziminde
=
karakterinden önce boşluk kullanmaya da izin verdiğini düşünün
p.x=(7)
p.x=7
p.x = 7
"Getters" tekrar
class Point
def initialize(x, y)
@x, @y = x, y
end
def x
@x
end
def y
@y
end
end
Her nitelik için böyle x
, y
metodlarına benzer "getters" mı yazacağız?
Bazı programlama dillerinde evet, her "public" nitelik için birer Getter yazılıyor
Ama Ruby'de gereksiz kod işçiliği sevilmez, bunun bir çözümü var
attr_reader
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
end
attr_reader
bir meta metotmeta? "kod yazan kod" gibi anlayabilirsiniz
attr_reader
Kendisine verilen her bir sembolü dikkate alarak aynı isimde birer okuyucu metot yazıyor
attr_reader :falan
ile eşdeğer kod
def falan
@falan
end
attr_reader
Ruby programlama dilinin (if
, def
gibi) bir anahtar kelimesi değil
Bu olağan bir metot
attr_reader :x, :y
'yiattr_reader(:x, :y)
olarak okuyunOkuyucu yazılacak metot adlarını birer dizgi veya (en güzeli ve yaygını) sembol olarak veriyoruz
Soru: Doğrudan
attr_reader x, y
yazsaydık ne olurdu?
Ruby'de buna benzer başka "meta" metotlar da var (ör.
private
)Bu sayede Ruby ile "meta programlama" yapabiliyoruz
Ruby'ye gerçek gücünü kazandıran bir özellik
"Setter" tekrar
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def x=(value)
@x = value
end
def y=(value)
@y = value
end
end
Her nitelik yazıcısı için böyle x=
, y=
metodlarına benzer "setters" mı yazacağız?
attr_writer
class Point
attr_reader :x, :y
attr_writer :x, :y
def initialize(x, y)
@x, @y = x, y
end
end
p = Point.new(3, 5)
p.x =7
p.y = 19
attr_accessor
class Point
attr_reader :x, :y
attr_writer :x, :y
def initialize(x, y)
@x, @y = x, y
end
end
yerine
class Point
attr_accessor :x, :y
def initialize(x, y)
@x, @y = x, y
end
end
attr_reader
, attr_writer
, attr_accessor
: bunlara genel olarak "nitelik erişimcileri" diyoruz
- Bunları hep aynı tipte kullanmanız gerekmiyor, farklı kombinasyonlar türebilirsiniz
x
'e yazılabilir ama okunamaz, y
sadece okunabilir (tuhaf bir kombinasyon)
class Point
attr_writer :x
attr_reader :y
def initialize(x, y)
@x, @y = x, y
end
end
x
'e hem yazılabilir hem okunabilir, y
sadece okunabilir (daha makul bir kombinasyon)
class Point
attr_accessor :x
attr_reader :y
def initialize(x, y)
@x, @y = x, y
end
end
veya
class Point
attr_reader :x, :y
attr_writer :x
def initialize(x, y)
@x, @y = x, y
end
end
Yazıcıları sadece nesnenin dışından değil nesne metotları içinde de kullanabilirsiniz
class Point
attr_accessor :x, :y
def initialize(x, y)
@x, @y = x, y
end
def reset
self.x = 0
self.y = 0
end
end
Dikkat!
Böyle değil
def reset
x = 0
y = 0
end
Böyle
def reset
self.x = 0
self.y = 0
end
İlk örnekte sorunun ne olduğunu görebiliyor musunuz? x = 0
satırında:
x
ismi bir yerel değişkeni mi anlatırYoksa
x=
metodunu mu anlatır
x =
denildiğinde tanımlayıcının çözülmesinde yerel değişken seçeneği ön plandadırself.x
denildiğinde tanımlayıcının çözülmesine daima önce metot seçeneğiyle başlanır
Veri sarmalama
Örneği inceleyelim
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def move_to_origin
reset
end
def to_s
"(#{x}, #{y})"
end
private
attr_writer :x, :y
def reset
self.x = 0
self.y = 0
end
...
end
p = Point.new(3, 5)
puts p # (3, 5) görüntülenir
p.move_to_origin
puts p # (0, 0) görüntülenir
Fakat...
p = Point.new(3, 5)
p.reset
p.x = 7
private
Erişim denetimi yapan bir "meta" metot
attr_reader
vs gibiKendisinden sonra tanımlanan tüm metotları "private" yapıyor
"private"? "mahrem", yani bu metotlara dışarıdan
.metot
şeklinde erişilmesine izin verilmiyor
Bunu bir metot adı verirseniz, "kendisinden sonra gelen tüm metotlar" yerine sadece adı verilen metotu private yapar
- Dikkat! Metot önceden tanımlanmış olmalı
class Point
attr_accessor :x, :y
def initialize(x, y)
@x, @y = x, y
end
def move_to_origin
reset
end
def to_s
"(#{x}, #{y})"
end
def reset
self.x = 0
self.y = 0
end
private :reset
end
public
Buna ilk başta değinmeye bile gerek duymadık
Erişim denetimi açıkça tanımlanmadığı sürece her şey "public"
Yani
private
(ve daha sonra değinilecek olanprotected
) yoksa daimapublic
Şöyle kombinasyonlar mümkün (ama yaygın değil), deneyin
class Falan
private
def this_is_private
end
def not_private_this_is_public
end
public :not_private_this_is_public
def also_private
end
public
def now_public
end
def not_public_this_is_private
end
private :not_public_this_is_private
def also_public
end
end
Veri Sarmalama
private
vepublic
nesneye erişimi denetliyorBu sayede nesnenin "paketlediği" veriyi sarmalamış oluyoruz
Sarmalanmış veri paketine erişim tamamen bizim denetimimizde
Ne kadar az şeyi dışarı açarsak o kadar iyi
Dışarıya açılan her şey, dış dünyaya verdiğiniz bir söz
Ne kadar az söz verirseniz o kadar çok rahat edersiniz
Dışarıya sunulması açıkça gerekmeyen her şeyi
private
yapınŞüphe halinde de ("belki bir gün birisi kullanır")
private
yapın, bırakın o gün gelsin
Bakın tipik bir örnek
class Foo
attr_reader :x, :y
def initialize(a, b)
@x = calculate_with(a)
@y = calculate_with(b)
end
private
def calculate_with(param)
...
end
end
Bu örnekte dış dünya için önemli olan
:x
ve:y
: dış işlericalculate_with
sizin iç işiniz: iç işleriİçeride dilediğiniz zaman dilediğiniz düzenlemeyi yapabilirsiniz
Örneğin gelecekte calculate_with
'i kaldırıp bunun yerine ayrı iki fonksiyon kullanmaya karar verdiniz
class Foo
attr_reader :x, :y
def initialize(a, b)
@x = calculate_for_x(a)
@y = calculate_for_y(b)
end
private
def calculate_for_x(a)
...
end
def calculate_for_y(b)
...
end
end
İç işlerindeki bu düzenlemenin dış dünya açısından bir önemi yok değil mi?
Peki ya
calculate_with
public
olsaydı?calculate_with
'i sancısız şekilde kaldıramazdınızÇünkü bu metot dış dünyaya verdiğiniz bir söz
Sözleri geri almak her zaman zordur
Nitelik yazıcıları tekrar
class Point
attr_accessor :x, :y
def initialize(x, y)
@x, @y = x, y
end
def to_s
"(#{x}, #{y})"
end
end
Soru: Nitelik yazıcılarını bu şekilde "public" yapmak doğru mu?
Nokta nesnesinin
x
vey
nitelikleri, nesne inşa edildikten sonra değiştirilebiliyorBu, nokta nesnesi müşterilerine verilmesi doğal olan bir kabiliyet mi?
Yazıcılar açık olmasaydı, koordinatlar hakkında noktanın oluşturulduğu yere bakarak akıl yürütebilirdik
Ama artık bu yeterli değil, nokta oluşturulduktan sonra değişmiş olabilir
Nokta tam olarak ne?
Noktanın doğal karakteri üzerinde duralım: asli özellikler
Asli özellikler: Bir nesneyi karakterize eden temel özellikler
Bir noktanın asli özellikleri:
x
vey
Yazıcılarla ilgili temel sorun:
Asli özellikleri değiştirilebilir yapmak nesnenin yaşam döngüsü içinde bazı kararların alınmasını güçleştiriyor
Bunu yapmayın!
Nitelik yazıcıları istisnai durumlarda yararlanabileceğiniz bir olanak olmalı
İstisnai durumlar arasında nesneyi karakterize eden temel nitelikler yok
Nesneleri inşa edildikten sonra olabildiğince "değiştirilemez" (immutable) yapmak bir norm olmalı
"İç işlerinde yazıcılara" belki ihtiyaç olabilir, nispeten kabul edilebilir bir çözüm:
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def to_s
"(#{x}, #{y})"
end
...
private
attr_writer :x, :y
...
end
Bu gerçeklemede yazıcılar sadece "iç işlerinde" kullanılabilir durumda
Yararı tartışılır:
@x =
yerineself.x =
yazabilme olanağı
Ama bakın şu kod daha anlamlı!
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def to_s
"(#{x}, #{y})"
end
...
private
def x=(value)
... # her x değişikliği girişimde gerçekleşecek bir işlem
@x = value
end
def y=(value)
... # her y değişikliği girişimde gerçekleşecek bir işlem
@y = value
end
...
end
Her nitelik değişiminde gerçekleşmesini istediğiniz işlem?
Örnekler:
Niteliğe atanacak değerin validasyonu
Nitelik değeri değiştiğinde buna bağlı bazı parametrelerin de değiştirilmesi (tekrar hesaplanması)
Kıssadan Hisse
Nesneleri kurgularken sorulması gereken ilk soru: "Nesnenin asli özellikleri nedir?"
Asli özellikleri nesne inşa edildiğinde ilkleyin ve sonrasında değiştirmeyin
Metotları sadece gerekliyse dışarı açın
Nitelik yazıcılarından kaçının
Noktanın yer değiştirmesi
Bir noktanın yerinin değiştirilmesi: move
metodu
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def move(delta_x, delta_y)
@x += delta_x
@y += delta_y
end
end
Bu metotun sorunları neler?
Nesnenin asli özelliklerini değiştiriyor
(birincil önemde olmayan sorun) metotun dönüş değeri anlamı değil
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
def move(delta_x, delta_y)
Point.new(@x + delta_x, @y + delta_y)
end
def to_s
"(#{x}, #{y})"
end
end
Noktanın yerini değiştirdiğimizde artık yeni bir noktadan söz etmeliyiz
Point.new
mevcut nokta nesnesi ve değişim miktarlarını dikkate alarak yeni bir nokta nesnesi üretiyormove
metodu bir nesne dönüyor
Kompozisyon
Gelin bir "Dikdörtgen" nesnesi tasarlayalım, bir dikdörtgeni nasıl tanımlarsınız?
y
^
|
| width
| +----------+
| | |
| | | height
| | |
| +----------+
|
|
+----------------------------------------------> x
Dikdörtgen eni ve boyu olan bir geometrik nesne
En ve boy dışında bir nitelik daha olmalı mı?
Eni ve boyu aynı olan iki farklı dikdörtgen?
Dikdörtgenin "pozisyonu"
Pozisyon? Dikdörtgenin köşe noktalarından biri yeterli
Pozisyon bir nokta, yani nokta nesnesi
Standart olarak "sol üst" köşe noktası ("top-left") seçilebilir
Dikdörtgen tekrar
Dikdörtgen en ve boy uzunluklarıyla karakterize edilen, sol üst köşe noktası olan bir geometrik nesnedir
class Rectangular
attr_reader :position, :width, :height
def initialize(point, width, height)
@position = point
@width = width
@height = height
end
end
Bu gerçeklemede
position
niteliği bir nokta nesnesi (point
)Yani? Bir nesnenin (
Rectangular
) kompozisyonunda başka nesneler (Point
) olabilirNYP metodolojisinde çok güçlü bir tasarım enstrümanı → Kompozisyon
Kompozisyon sayesinde önceden yazılmış kodları tekrar kullanabiliyoruz
Buna "Code Reuse" deniliyor
NYP'nin önemli vaadlerinden biri
Kompozisyon "Code Reuse" için bir enstrüman
Başka enstrümanlar da var: ör. "Kalıtım" (buna geleceğiz)
Bu örnekte "Code Reuse" nerede/nasıl gerçekleşiyor?
Dikdörtgenin pozisyonu bir nokta nesnesi
Dikdörtgenin pozisyonuyla ilişkili işlemleri bu nesneye delege edebiliriz
Delege edilen işlemleri
Rectangular
içinde gerçeklememiz gerekmiyorÖrnek? Dikdörtgenin yer değiştirmesi
class Rectangular
attr_reader :position, :width, :height
def initialize(point, width, height)
@position = point
@width = width
@height = height
end
def move(delta_x, delta_y)
@position = @position.move(delta_x, delta_y)
end
end
Soru: Bu gerçekleme position
niteliğini değiştiriyor?
Evet, eldeki probleme göre bu hatalı bir tasarım olabilir
Ama mermere kazınmış mutlak bir kanun değil bu
Tevil edelim,
position
dikdörtgenin asli bir özelliği mi?Asli özellikler en ve boy olarak yorumlanabilir
Eni ve boyu aynı iki dikdörtgenin pozisyonlar farklı olsa bile bizdeki görsel algısı benzer
class Rectangular
attr_reader :position, :width, :height
def initialize(point, width, height)
@position = point
@width = width
@height = height
end
def move(delta_x, delta_y)
@position = @position.move(delta_x, delta_y)
end
def to_s
"[#{width}x#{height}]@#{position}"
end
end
def main
r = Rectangular.new(Point.new(0, 0), 3, 5)
puts r
end
to_s
metoduna Point
nesnesinin katkısını not edin
Dikdörtgen pozisyonunun metinsel tarifini nokta nesnesine delege ettik
Delegasyona daha ayrıntılı bakacağız
Rectangular
nesnesinin nasıl inşa edildiğine bakın
class Rectangular
attr_reader :position, :width, :height
def initialize(point, width, height)
@position = point
@width = width
@height = height
end
...
end
def main
r = Rectangular.new(Point.new(0, 0), 3, 5)
puts r
end
İnşa edici 3 "pozisyonel" argüman alıyor:
position
,width
,height
Genel ilke: bir metoda "pozisyonel" yolla geçirilen argümanların sayısı çok olmamalı
Neden? Pozisyon hatası yapılabilir, okunurluk azalır
Örnekte bu sayı 3, ayrıca ilk argümanla diğer ikisi karakter olarak çok farklı
Müşteri kodundaki görüntü sıkıntılı:
r = Rectangular.new(Point.new(0, 0), 3, 5)
Pozisyonel argüman sayısı fazlaysa "İsimlendirilmiş argüman" kullanın
class Rectangular
attr_reader :position, :width, :height
def initialize(position:, width:, height:)
@position = position
@width = width
@height = height
end
...
end
def main
r = Rectangular.new(position: Point.new(0, 0), width: 3, height: 5)
puts r
end
Hatta bir parça "öntanımlılığa" yaslanabiliriz
position
verilmezse orijin noktası alınsın
class Rectangular
attr_reader :position, :width, :height
def initialize(position: Point.new(0, 0), width:, height:)
@position = position
@width = width
@height = height
end
...
end
def main
r = Rectangular.new(width: 3, height: 5)
puts r
end
Örnek: Dikdörtgenin SVG çizimi
Verilen bir dikdörtgen nesnesinin SVG formatında vektörel çizimini elde edin
Delegasyon
Kompozisyon ile el ele görünen bir terim
- Nesne arayüzünde yer alan bazı metotların kompozisyona giren iç nesneye havale edilmesi
Dikdörtgenin (pozisyonun) merkez noktasına olan uzaklığı
class Point
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def distance
Math.sqrt((@x * @x) + (@y * @y))
end
end
class Rectangular
attr_reader :position, :width, :height
def initialize(position: Point.new(0, 0), width:, height:)
@position = position
@width = width
@height = height
end
def distance
@position.distance
end
end
def main
r = Rectangular.new(position: Point.new(1, 0), width: 3, height: 5)
puts r.distance
end
Uzaklık hesabı içteki pozisyon nesnesine delege edilmiş
...
def distance
@position.distance
end
...
Kompozisyonda sıklıkla karşılaşılan bir durum
Bu örnekte arayüzdeki sadece bir metot delege ediliyor
Olağan senaryolarda bu şekilde delege edilen pek çok metot olabilir
Kompozisyon gerçek gücünü bu tür delegasyonlarda buluyor
Ana nesne problemin tüm unsurlarıyla tek başına mücadele etmiyor
Yeri geldiğinde sorumluluğu kompoze edilen iç nesnelere havale ediyor
Delegasyon karmaşıklıkla mücadele etmenin bir yolu
Gerçek hayatta da böyle değil mi?
Yoğun iş yüküyle mücadele etmenin en etkili yollarından biri: gelen bazı işleri yardımcılara havale etmek
Dışarıdan bakıldığında sorumlu biziz ama gerçekte işi yapan bir başkası
Forwardable
modülü
Madem ki sık karşılaşılıyor, Ruby'de "delegasyon"u kolaylaştırıcı özellikler var mı?
Evet var
En bilineni
Forwardable
modülü (ama bununla sınırlı değil)
require 'forwardable' # Standart kitaplıktan yükle
class Point
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def distance
Math.sqrt((@x * @x) + (@y * @y))
end
end
class Rectangular
extend Forwardable # Delegasyon için gerekli DSL'i (ör. def_delegator) etkinleştir
def_delegator :@position, :distance # Delegasyonu tanımla
attr_reader :position, :width, :height
def initialize(position: Point.new(0, 0), width:, height:)
@position = position
@width = width
@height = height
end
end
def main
r = Rectangular.new(position: Point.new(1, 0), width: 3, height: 5)
puts r.distance
end
Katıştırma: extend
Forwardable
modülünüRectangular
nesnesine "katıştırıyoruz"Katıştırma → "mixin"
Katıştırma
include
veyaextend
metotlarıyla gerçekleşiyorBu örnekte
extend
(neden olduğuna daha sonra değineceğiz)
class Rectangular
extend Forwardable
def_delegator :@position, :distance
...
end
extend Forwardable
?
Forwardable
modülünde delegasyon tanımlayan bir DSL varBu satır sayesinde tüm
Rectangular
nesnelerinde sınıf düzeyindedef_delegator
DSL metotu kullanılabiliyor
def def_delegator :@position, :distance
?
Rectangular
nesnesinindistance
metotu uyarıldığında@position
nesnesinin aynı isimli metotu uyarılıyordef_delegator
aslında basitçedistance
adında bir metot yazıyor, tanıdık geldi mi?
Katıştırma: include
Ruby'de "katıştırma"nın daha yaygın bir şekli
include
Şimdi bunu örnekleyelim
Dikdörtgen nesnelerini sırala
Elimizde rastgele sırada dikdörtgen nesnelerinden oluşan bir dizi var
Diziyi nasıl sıralarız?
...
def main
rectangulars = [
Rectangular.new(position: Point.new(0, 0), width: 3, height: 5),
Rectangular.new(position: Point.new(1, 0), width: 2, height: 4),
Rectangular.new(position: Point.new(0, 0), width: 10, height: 6),
Rectangular.new(position: Point.new(2, 4), width: 1, height: 2)
]
rectangulars.sort.each do |r|
puts r
end
end
Sıralamanın ölçüsü ne olmalı? Bunu biz tanımlayacağız
İki dikdörtgenden alanı küçük/eşit/büyük olan diğerinden küçük/eşit/büyüktür, pozisyondan bağımsız olarak
Tanım neyse sıralama da ona göre yapılacak
Şu anki tanımımız bu, unutmayın bu bir tanım, farklı da olabilirdi
class Rectangular
include Comparable
attr_reader :position, :width, :height
def initialize(position: Point.new(0, 0), width:, height:)
@position = position
@width = width
@height = height
end
def area
width * height
end
def <=>(other)
area <=> other.area
end
end
Comparable
modülü
"Sıralanabilir" nitelikteki türdeş bir nesne topluluğunda nihai "sıralama" işlemini yapıyor
Nasıl?
"Sıralama"da ne yapıldığını daha iyi anlamamız lazım
"Sıralama" bir kolleksiyonda dolaşarak yapılan bir işlem
Her seferinde kolleksiyonda iki öge seçiliyor
Seçilen iki öge karşılaştırılıyor
Karşılaştırma üç sonuçlu bir işlem: küçük, eşit, büyük
Sıralama algoritması
Kolleksiyonda rastgele bir sırayla dolaşmamalıyız, bunun "optimum" bir yolu olmalı
Kolleksiyonda dolaşma ve öge çiftinin seçimi "sıralama algoritması"nın konusu
Sıralama algoritması? Quick sort, Bubble Sort, Merge Sort vb
Sıralama algoritması kolleksiyonu oluşturan türdeş nesnelerin ne olduğundan bağımsız
Sıralamanın probleme yani nesne türüne bağlı olan tek tarafı "karşılaştırma"
Comparable
modülü "bana karşılaştırma lojiğini ver, sıralamayı ben hallederim" diyen bir modülKarşılaştırma lojiği →
<=>
metodu
Karşılaştırma: <=>
Üç sonuçlu bir işlem: küçük, eşit, büyük
Bu nedenle
<=>
gibi bir gösterimi varSayısal olarak ifade edecek olursak: negatif, sıfır, pozitif
Buna "spaceship operator" da deniliyor (cepheden bakıldığında bir uzay gemisi)
Örneğimizde karşılaştırma lojiği
class Rectangular
...
def <=>(other)
if area < other.area
-1 # negatif herhangi bir sayı da olur
elsif area > other.area
1 # pozitif herhangi bir sayı da olur
else
0 # eşit ise daima 0
end
end
end
Veya...
class Rectangular
...
def <=>(other)
area <=> other.area
end
end
Comparable
: "Bana yeter ki karşılaştırma lojiğini ver, ben de sana en optimum sıralama metotlarını sağlayacağım!"
include Comparable
ne yapıyor? İlgili nesnelerden oluşan dizidesort
ailesindeki metotları sağlıyorDikkat buyurun, bu metotlar
Rectangular
nesnesinde tanımlı değil
...
def main
rectangulars = [
Rectangular.new(position: Point(0, 0), width: 3, height: 5),
Rectangular.new(position: Point(1, 0), width: 2, height: 4),
Rectangular.new(position: Point(0, 0), width: 10, height: 6),
Rectangular.new(position: Point(2, 4), width: 1, height: 2)
]
rectangulars.sort.each do |r|
puts r
end
end
"Katıştırma" konusuna daha sonra ayrıntılı değineceğiz
Veri sarmalama: protected
Gelin karşılaştırma lojiğini değiştirelim
Karşılaştırma bağlamında dikdörtgen "büyüklüğünün" ölçüsü olarak alan dışında orijine olan uzaklığı da hesaba katalım
Hayali bir nitelik:
bigness
bigness
→distance
xarea
class Rectangular
include Comparable
extend Forwardable
def_delegator :@position, :distance
...
def area
weight * height
end
def bigness
distance * area
end
def <=>(other)
bigness <=> other.bigness
end
end
Fakat bigness
dışarı açık tutmak istemediğimiz bir nitelik
Sadece karşılaştırmada anlamlı bir nitelik
Dış dünya (müşteriler) bunu bilmese de olur, bu bizim iç işimiz
Veri sarmalama → Dışarı olabildiğince az bilgi sızdır
O halde bigness
private
olmalı?
class Rectangular
include Comparable
extend Forwardable
def_delegator :@position, :distance
...
def area
weight * height
end
def <=>(other)
bigness <=> other.bigness
end
private
def bigness
distance * area
end
end
Fakat bir problemimiz var
Karşılaştırma sırasında
other.bigness
çağrısı hata üretirÇünkü
other
nesnesininprivate
bir metodunu uyarıyoruz
Şunu diyebiliyor muyuz?
"Evet gizli, fakat aile arasında sır olmasın"
Aile? Aynı türde, yani aynı sınıf veya mirasçılarından inşa edilmiş nesneler
Evet bunu yapabiliyoruz; private
yerine protected
kullanarak
class Rectangular
include Comparable
extend Forwardable
def_delegator :@position, :distance
...
def area
weight * height
end
def <=>(other)
bigness <=> other.bigness
end
protected
def bigness
distance * area
end
end
protected
niteleyicisi
private
'ın gevşetilmiş haliAynı türde, yani aynı sınıf veya mirasçılarından inşa edilmiş nesneler üzerinden uyarılabilen metotlar
Dışarı sızdırmak istemediğimiz iç işleri kontratları için kullanılıyor
Gerçek hayat problemlerinde (sayıları çok yüksek olmasa da) böyle durumlar var
Örneğimizdeki "iç işleri kontratı" →
bigness
Meta metotlar
to_s
<=>
Böyle "işe yarar" başka metotlar var mı?
eql?
: Eşdeğer mi?
to_s
ve<=>
gibi bir meta metotHer nesnede (en üst ata sınıftan miras alınarak gelen) bir varsayılan gerçeklemesi bulunuyor
Varsayılan? (Basitleştirecek olursak) nesnelerin
object_id
leriDiğer meta metotlarda olduğu gibi varsayılanı değiştirmek ne işe yarıyor? Örnekleyeceğiz
Problem: Rectangular
nesnelerinden oluşan bir diziyi tekilleştir
Dizide tekrar eden nesneleri çıkar
Tekrar eden nesneler? Aynı nitelikteki yani "eşdeğer" dikdörtgenler
Yapılan işlemin adı:
uniq
, tekilleştirme
Eşdeğerliğin ölçüsü ne olmalı? Bunu biz tanımlayacağız
İki dikdörtgenin pozisyonu, eni ve boyu aynı ise bu dikdörtgenler eşdeğerdir
Tanım neyse eşdeğerlik denetimi de ona göre yapılacak
Şu anki tanımımız bu, unutmayın bu bir tanım, farklı da olabilirdi
Dizideki tekrarı görebiliyor musunuz? Rectangular.new(Point(0, 0), 3, 5)
nesneleri
...
def main
rectangulars = [
Rectangular.new(position: Point.new(0, 0), width: 3, height: 5),
Rectangular.new(position: Point.new(1, 0), width: 2, height: 4),
Rectangular.new(position: Point.new(0, 0), width: 3, height: 5),
Rectangular.new(position: Point.new(2, 4), width: 4, height: 2)
]
puts rectangulars.uniq.size # 3
end
class Point
...
def eql?(other)
[x, y] == [other.x, other.y]
end
...
end
class Rectangular
...
def eql?(other)
[position, width, height] == [other.position, other.width, other.height]
end
...
end
Bakın bu örnekte "pozisyonların eşdeğerliği"ni
Point
nesnesine havale ettikEşdeğerlik tanımını değiştirebiliriz
Örneğin sadece alanları aynı olan dikdörtgenleri eşdeğer kabul etmek
Bu yapıldığında sonuç ne olur? Bu tanıma uygun
eql?
metodunu yazabilir misiniz?
Sınıf metotları
İnşa edici metota bakalım
class Rectangular
attr_reader :position, :width, :height
def initialize(position: Point.new(0, 0), width:, height:)
@position = position
@width = width
@height = height
end
...
end
def main
r = Rectangular.new(width: 3, height: 5)
puts r
end
Öntanımlı orijin noktası için daha okunur bir düzenleme yapabilir miyiz?
class Rectangular
attr_reader :position, :width, :height
def initialize(position: Point.origin, width:, height:)
@position = position
@width = width
@height = height
end
...
end
def main
r = Rectangular.new(width: 3, height: 5)
puts r
end
Point.origin
origin
nasıl bir metot?Metodu ne üzerinden uyarıyoruz?
.
'nın solunda ne var?
Point.origin
Bu bir sınıf metodu
Çünkü metot bir nesne üzerinden değil bir sınıf üzerinden uyarılıyor
Böyle başka metotlarla karşılaştınız mı? Evet,
new
new
özel bir sınıf metotu (ama üzerine yazılabilir)
Bir sınıf tanımında
Nesne üzerinden uyarılacak şekilde tanımlanan bir metota nesne metodu ("instance method") diyoruz
Sınıf üzerinden uyarılacak şekilde tanımlanan bir metota sınıf metodu ("class method") diyoruz
Yaygın olan ilki: hemen hemen daima nesne metotlarıyla çalışıyoruz
class Point
attr_reader :x, :y
def initialize(x, y)
@x = x
@y= y
end
def Point.origin
Point.new(0, 0)
end
end
Ama bunun yakışanı:
class Point
attr_reader :x, :y
def initialize(x, y)
@x = x
@y= y
end
def self.origin
new(0, 0)
end
end
Point
yerineself
yeterliSınıf metotunun gövdesinde
self
zatenPoint
'i gösterdiğindeself.new
yerinenew
yeterli
Miras alma
Bir Square
(kare) nesnesi gerçekleyelim
class Square
attr_reader :position, :side
def initialize(side, position: Point.origin)
@position = position
@side = side
end
def area
side * side
end
def periphery
4 * side
end
def to_s
"#{side}@#{position}"
end
...
end
def main
squares = [Square.new(3), Square.new(1, position: Point.new(3, 5))]
squares.each do |square|
puts square
end
end
Rectangular
nesnesi için yapılan gerçeklemeyi neredeyse tekrar ediyoruz
"Code reuse" yapıyoruz
Rectangular
gerçeklemesindeki kodları kullanmanın bir yolu yok mu?
Rectangular
gerçeklemesini hatırlayalım
class Rectangular
attr_reader :position, :width, :height
def initialize(position: Point.origin, width:, height:)
@position = position
@width = width
@height = height
end
def area
width * height
end
def periphery
2 * (width + height)
end
def move(delta_x, delta_y)
@position = @position.move(delta_x, delta_y)
end
def to_s
"[#{width}x#{height}]@#{position}"
end
end
Miras alma (inheritance) kullanacağız
class Square < Rectangular
...
end
Sözdizimi
Çocuk sınıf < Ata sınıf
"
Square
bir türRectangular
'dır" demiş oluyoruzYa da "
Square
özellikleriniRectangular
'dam miras alır"
Fakat farklılıklar var, bunları nasıl yöneteceğiz?
CTOR'lar farklı
square = Square.new 3, position: Point.new(3, 5) rectangular = Rectangular.new width: 3, height: 3, position: Point.new(3, 5)
Metinsel temsiller farklı (yukarıdaki örnekler için)
puts square # 3@(3, 5) puts rectangular # [3, 3]@(3, 5)
Öte yandan bunların dışındaki neredeyse tüm metotlar aynı
Miras alma mekaniği
Denemeler yapalım
class A
def initialize
end
def meth_inside_a
puts 'meth_inside_a'
end
def some_other_meth
puts 'defined in a'
end
end
class B < A
def meth_inside_b
puts 'meth_inside_b'
end
def some_other_meth
puts 'defined in b'
end
end
a = A.new
b = B.new
a.meth_inside_a
a.some_other_meth
b.meth_inside_a
b.meth_inside_b
b.some_other_meth
a.meth_inside_b
Ata sınıf gereçeklemesi çocuk sınıfa geçiyor
Çocuk sınıf Ata sınıftan miras aldığı bazı metotların üzerine yazmış
"Üzerine yazma" sadece çocuk sınıfı etkiliyor, Ata sınıf etkilenmiyor
O halde şöyle olabilir mi?
class Square < Rectangular
attr_reader :position, :side
def initialize(side, position: Point.origin)
@position = position
@side = side
end
def area
side * side
end
def periphery
4 * side
end
def to_s
"#{side}@#{position}"
end
...
end
İyi bir başlangıç , fakat çok verimsiz:
area
,periphery
gerçeklemeleri tekrar ediyorTemel sorun
Rectangular
'daki tüm metotlardawidth
,height
ayrımı olmasıBu ayrımı koruyup sadece
width = height = side
özelleştirmesi yapan bir çözüm lazım
class Square < Rectangular
attr_reader :side
def initialize(side, position: Point.origin)
super(position: position, width: side, height: side)
@side = side
end
def to_s
"#{side}@#{position}"
end
end
super
Çağrıldığı noktada içinde bulunduğu metodun Ata sınıftaki karşılığını çalıştırır
class A
def meth
puts 'meth in A'
end
end
class B < A
def meth
super
puts 'meth in B'
end
end
a = A.new
b = B.new
b.meth
Dikkat! Argüman vermeden salt super
olarak çağrılırsa içinde bulunduğu metoda geçirilen tüm argümanları Ata sınıftaki metoda geçirir
class A
def meth(x)
puts "meth in A: #{x}"
end
end
class B < A
def meth(x)
super
puts 'meth in B'
end
end
a = A.new
b = B.new
b.meth 42
super
'in kullanıldığı metot argüman alıyor fakat Ata sınıftaki metod argüman almıyorsa super()
şeklinde kullanın
Şecere (Soy ağacı)
Nesnenin soy ağacını inceleyelim
class A; end
class B < A; end
A.ancestors #=> [A, Object, Kernel, BasicObject]
B.ancestors #=> [B, A, Object, Kernel, BasicObject]
Object.ancestors #=> [Object, Kernel, BasicObject]
Kernel.ancestors #=> [Kernel]
BasicObject.ancestors #=> [BasicObject]
- Ruby'de tüm nesnelerin görünmeyen ataları var: en tepede
BasicObject
, altındaObject
Fakat Kernel
böyle değil (bu bir nesne mi)? Ruby'de bir şeyin ne olduğunu sorgulamak için class
'ına bakın
class A; end
class B < A; end
A.class #=> Class
B.class #=> Class
Object.class #=> Class
Kernel.class #=> Module
BasicObject.class #=> Class
class
metoduylaClass
tipini karıştırmayınA.class #=> Class
şunu diyor: A sabitiyle gösterilen şey bir sınıf
Kernel.class #=> Module
: Kernel sabitiyle gösterilen şey bir modül
İşte bu ilginç, modüller bir nesnenin nasıl atası olabiliyor?
Bir başka soru? Bir nesne aynı anda (seviyede) birden fazla atadan miras alabilir mi?
Katıştırma (mixin) tekrar
Katıştırmanın sırrı
module M; end
class A; end
class B < A; include M; end
B.ancestors #=> [B, M, A, Object, Kernel, BasicObject]
include M
ile bir modülün bir sınıfa katıştırılması da bir "miras alma etkisi" yaratıyor
M
'ninA
'dan önce geldiğini not edin, neden? ÖnceB < A
etkin sonrainclude M
Karşılaştıralım
module M
def some_meth
puts 'some_meth'
end
end
class A
include M
end
ile
class M
def some_meth
puts 'some_meth'
end
end
class A < M
end
Görüldüğü gibi some_meth
metodunun varlığı açısından ikisi de aynı nihai sonucu veriyor, fakat ciddi bazı farklar var
class A < M
statik bir miras almadır, miras alma ilişkisi çok erken kuruluyorinclude M
dinamik bir miras alma, miras alma ilişkisi çalışma zamanında daha geç fazda kuruluyor
Çoklu miras alma
Multiple inheritance
Ruby'de "statik" anlamda çoklu miras alma yoktur, aynı anda sadece bir Ata'dan miras alabilirsiniz
Fakat katıştırma yoluyla çoklu miras alma mümkün
module M; end
class A; end
class B < A; include M; end
B.ancestors #=> [B, M, A, Object, Kernel, BasicObject]
Bakın B
'nin iki atası var M
ve A
Bir tür çoklu miras alma
Ama
M
'in yine de bir modül olduğunu unutmayın
Dinamik miras alma
Miras alma ilişkisi
<
operatörüyle kurulduğunda bu bir statik miras almaBu operatörü kullanmadan da miras alma ilişkisi kurulabiliyor
Buna dinamik miras alma diyoruz
Örneğin katıştırmanın bir tür dinamik miras alma olduğunu artık biliyoruz
module M; end
class A; end
class B < A; end
class C; include M; end
D = Class.new A
Bu örnekte
D
nesnesiA
nesnesinden dinamik olarak miras alıyor"
D
adında bir sınıf oluştur, öyle ki atasıA
olsun"
Bu tür dinamik miras alma ata sınıfın çalışma zamanında belirlendiği senaryolarda yararlı
Bu çok olağan bir senaryo değil
Bir dinamik programlama dili olan Ruby'nin gücünü gösteren etkili bir örnek
Dinamik miras almada sınıf gövdesi nerede? Basitçe blok kullanıyoruz
D = Class.new A do
...
end
Bazı senaryolarda bu çok önemli bir avantaj sunuyor: "Closures"
Daha sonra değineceğimiz bir avantaj
Ama genel olarak dinamik miras almadan kaçınıyoruz, basit olanı (statik miras alma) varken
- Daha olağan bir senaryo: özel "İstisna" ("Exception") nesneleri oluşturmak
ParseError = Class.new StandardError
İstisnalar
Programda ortaya çıkan beklenmedik, yani "istisnai" durumları yönetmekte kullanılıyor
>> 3/0
(irb):1:in `/': divided by 0 (ZeroDivisionError)
from (irb):1:in `<main>'
...
ZeroDivisionError
: Sıfıra bölme hatası
İstisnanın hatayı temsil eden bir nesne olduğuna dikkat edin
Nesnenin türü → İstisna sınıfı
Örnekte
ZeroDivisionError
Bu güne kadar istisnalarla çok karşılaştık
- Çünkü Ruby (ve benzeri pek çok programlama dilinde) hataları hemen hemen daima istisnalar ile yönetiyoruz
SyntaxError
: Söz dizimi hatası
>> 0rder = 5
...in `eval': (irb):5: syntax error, unexpected local variable or method, expecting end-of-input (SyntaxError)
0rder = 5
^~~~
- Tanımlayıcılar rakamla başlayamaz
NameError
: İsim hatası
>> puts x
(irb):7:in `<main>': undefined local variable or method `x' for main:Object (NameError)
...
x
ismi herhangi bir değişken veya metot adı değil
ArgumentError
: Argüman hatası
>> "foo bar".delete_prefix
(irb):2:in `delete_prefix': wrong number of arguments (given 0, expected 1) (ArgumentError)
from (irb):2:in `<main>'
...
NoMethodError
: Olmayan metot hatası
>> 3.foo
(irb):3:in `<main>': undefined method `foo' for 3:Integer (NoMethodError)
Did you mean? floor
...
- Tamsayılarda
foo
adında bir metot yok
Çok sık karşılaşılan bir NoMethodError
hatası
>> i = nil
=> nil
>> i.foo
(irb):10:in `<main>': undefined method `foo' for nil:NilClass (NoMethodError)
...
nil
nesnesindefoo
adında bir metot yok
Dinamik dillerin laneti!
Bu hatayla "çalışma zamanında" sık karşılaşabiliriz
Bir nesneye eriştik, ama bu örnekte olduğu gibi
nil
olduğu açık şekilde görülen biri
erişimi değilBelki bir işlemin sonucuyla veya metota geçirilen bir argümanla
Kodu okuduğumuzda nesnenin
nil
olacağına ilişkin bir belirti yok, hava aydınlıkBağlama göre nesnenin tipini ve dolayısıyla hangi metotlara cevap verdiğini biliyoruz
Fakat işe bakın ki bir programlama hatası olarak nesne boş yani
nil
çıkıyor
def uppercase_first_on_match(strings, character)
strings.find { |string| string.include? character }.upcase
end
uppercase_first_on_match %w[apple orange banana melon], 'a' #=> APPLE
Peki ya böyle?
uppercase_first_on_match %w[apple orange banana melon], 'i' # undefined method `upcase' for nil:NilClass (NoMethodError)
Bu tür hataların doğru yönetilmesi lazım
- Doğru yönetmek → Doğru yerde, doğru şekilde yönetmek
Nesnenin nil
olma ihtimali varsa (bu durum olağan yani istisnai değilse)
def uppercase_first_on_match(strings, character)
string = strings.find { |string| string.include? character }
string.upcase if string
end
veya
def uppercase_first_on_match(strings, character)
string = strings.find { |string| string.include? character }
string&.upcase
end
Nesnenin nil
olması olağan değilse, yani bu beklenmeyen bir durumsa
- Hatayı hangi katmanda yöneteceğimiz bakmamız lazım
Sorular
Hatayı bununla karşılaştığımız metot içinde mi yönetelim?
Yoksa bu metotu çağıran tarafta mı yönetelim?
Hatayı yaşadığımız nesne bizim denetimimizde değilse metot içinde yönetmeyi tercih edebiliriz
def uppercase_first_on_match(strings)
string = strings.find { |string| string.include? 'a' }
raise 'At least one string must have an "a"' unless string
string.upcase
end
Hata üret: raise
İstisna üretiyor
Genel formu:
raise [«İSTİSNA SINIFI», ] «HATA İLETİSİ»
Örnekte bir istisna sınıfı yani hata tipi tanımlamadık
raise
hata tipi tanımlanmadığındaRuntimeError
tipinde bir hata üretirBu doğru bir pratik değil, hata tipini tanımlamamız lazım
Neden? Bunun üzerinde daha sonra duracağız
def uppercase_first_on_match(strings)
string = strings.find { |string| string.include? 'a' }
raise ArgumentError, 'At least one string must have an "a"' unless string
string.upcase
end
Hata tipini
ArgumentError
olarak verdik, "geçirilen argümanda bir hata var" niyetiyleArgumentError
Ruby'nin yerleşik hata (istisna) tiplerinden biriÖnceki örneğe göre daha iyi ama en uygunu özel bir hata tipi oluşturmak (buna geleceğiz)
Nesnenin kaynağı denetimimizde ise çağıran tarafta yönetmeyi de tercih edebiliriz
def sanitize_strings(strings)
return strings if strings.any? { |string| string.include? 'a' }
raise 'At least one string must have an "a"'
end
def uppercase_first_on_match(strings)
strings.find { |string| string.include? 'a' }.upcase
end
uppercase_first_on_match sanitize_strings(%w[apple orange banana melon]) #=> APPLE
uppercase_first_on_match sanitize_strings(%w[lemon melon]) # undefined method `upcase' for nil:NilClass (NoMethodError)
Bazen (kritik kod yollarında) defansif programlama yapmak adına hatayı her iki tarafta yönetebiliriz
def uppercase_first_on_match(strings)
string = strings.find { |string| string.include? 'a' }
return string.upcase if string
raise 'Unexpected strings: expected to have a letter "a" in at least one string'
end
uppercase_first_on_match sanitize_strings(%w[apple orange banana melon])
uppercase_first_on_match (%w[lemon melon])
Hata yakala: rescue
def uppercase_first_on_match(strings)
string = strings.find { |string| string.include? 'a' }
raise ArgumentError, 'At least one string must have an "a"' unless string
string.upcase
end
def main
puts uppercase_first_on_match ARGV
rescue ArgumentError
abort 'Error occured'
end
main
rescue
'nun hangi girinti seviyesinde yazıldığına dikkat edin!Bu aslında bir kısa yol
begin
/ end
def main
begin
puts uppercase_first_on_match ARGV
rescue ArgumentError
abort 'Error occured'
end
end
begin
/end
bir ifade grubu oluşturuyorBurada özel bir durum var, grup zaten metot gövdesine oturmuş durumda
Ruby bu özel durumda bir kısa yol sunuyor, özel bir
begin
/end
bloğu yazmadan şöyle:
def main
puts uppercase_first_on_match ARGV
rescue ArgumentError
abort 'Error occured'
end
Fakat bazı senaryolarda kısa yolu kullanmadan açık halde yazmalısınız
def main
begin
puts uppercase_first_on_match ARGV
rescue ArgumentError
err = 'Error occured'
end
puts 'Operation done.'
abort if err
end
begin
/end
verilen örneklerdeki gibi hata yönetiminde sık kullanılıyorPratikte karşılaşılan diğer bir senaryo grubun dönüş değerini kullanmak
Bu senaryoda yeni bir yardımcı metot yazmak yerine lojiği
begin
/end
ile gruplamasına alıyoruzBu teknik çok kısa lojikler için yararlı; lojiği küçük bir metota almadan yerinde gerçekliyoruz
Yardımcı metoda alsaydık bir de o metoda bakmamız gerekeceğinden okunurluk zorlaşacaktı
Ama bu tekniği kötüye kullanmayın, lojik basit değilse mutlaka metoda bölün
def language_data_for(language)
self.data[language] ||= begin
(file = language_file_for(language)) ? YAML.load_file(file) : {}
end
end
Hata yönetimine geri dönelim
def uppercase_first_on_match(strings)
string = strings.find { |string| string.include? 'a' }
raise ArgumentError, 'At least one string must have an "a"' unless string
string.upcase
end
def main
puts uppercase_first_on_match ARGV
rescue ArgumentError
abort 'Error occured'
end
main ARGV
- Bir sorun var, hata iletisi bu haliyle yararsız, hata hakkında ilave bilgi verilmemiş
Hata detayına nasıl erişeceğiz? Üretilen hata nesnesiyle
Hata iletisi hata nesnesi içinde bulunuyor
Hata nesnesine nasıl erişeceğiz?
Şöyle:
rescue «HATA TİPİ» => «HATA NESNESİNİ TAŞIYACAK DEĞİŞKEN»
def uppercase_first_on_match(strings)
string = strings.find { |string| string.include? 'a' }
raise ArgumentError, 'At least one string must have an "a"' unless string
string.upcase
end
def main
puts uppercase_first_on_match ARGV
rescue ArgumentError => e
abort "Error occured: #{e.message}"
end
main
Örnekte hata nesnesi için
e
gibi kısa bir ismi tercih ettikBaşka bir isim de olabilir ama bu isim çok yaygın ve deyimsel
Hata iletisi nerede?
e
nesnesininmessage
niteliğinde
Hata yönetiminde hata yapılabilir mi? Örnekteki hatayı görebiliyor musunuz?
def uppercase_first_on_match(strings)
string = strings.find { |string| string.include? 'a' }
raise ArgumentError, 'At least one string must have an "a"' unless string
string.upcase
end
def main
puts uppercase_first_on_match
rescue ArgumentError => e
abort "Error occured: #{e.message}"
end
main
uppercase_first_on_match
metodunaARGV
argümanını geçirmeyi unuttukNe oldu?
uppercase_first_on_match
beklediği argümanı alamadığından kendisi de birArgumentError
ürettiHatalar birbirine karışıyor, bu
ArgumentError
farklı türde bir hataÖzel bir hata tipi oluşturma yerine standart hataları kullanmamızın bedeli
Özel bir hata tipi üretmemiz lazım, nasıl?
StringsError = Class.new StandardError
def uppercase_first_on_match(strings)
string = strings.find { |string| string.include? 'a' }
raise StringsError, 'At least one string must have an "a"' unless string
string.upcase
end
def main
puts uppercase_first_on_match ARGV
rescue StringsError => e
abort "Error occured: #{e.message}"
rescue ArgumentError => e
abort "Programming error: #{e.message}"
end
main
Aynı metot içinde birden fazla hata tipinin yönetilebileceğine dikkat edin!
Kendi hata tipimizi nasıl oluşturduk? Dinamik miras almayla
Ama statik miras almayla da olur
class StringsError < StandardError; end
Kalıtım yoluyla sadece yeni bir hata tipi ürettik, gövdesi olmayan bir sınıf?
Evet, bu çok olağan bir senaryo
Çoğu durumda fazlasına ihtiyacımız yok
Bize tek gereken hatanın tipi, diğer hatalardan ayrılması için
Deyimsel olarak bunu hemen hemen daima
StandardError
yerleşik hatasından miras alarak yapıyoruz
Hata tipi yeterli değilse, özel şeyler yapan bir hata tipi oluşturmak istiyorsanız? Boşlukları doldurun
class MyError < StandardError
...
end
veya
MyError = Class.new StandardError do
...
end
- İlk form daha doğru, ikinci formu sadece basit senaryo için öneriyoruz
En temelde şunu yapmaya çalıştık
Hata yönetimini en tepe seviyede (
main
fonksiyonu) yönettikBunu yapmasaydık son kullanıcı muhtemelen ona anlamsız gelecek istisna iletileriyle karşılacaktı
Bu bir UX duyarlılığı, son kullanıcıyı istisna iletileriyle karşı karşıya bırakmayın
Ruby hata hiyerarşisi
StandardError
yerleşik hatasından miras alıyoruz
Özel hata tiplerinde hemen hemen daima
StandardError
'den miras alıyoruzBu Ruby'nin yerleşik hata tiplerinden biri
Bunların bir kısmını zaten biliyorsunuz, ör.
NameError
,ArgumentError
Ruby'nin diğer hata tiplerini ve soy ağacını inceleyin: https://ruby-doc.org/core-3.0.1/Exception.html
İstisnaları sadece istisnai durumlarda kullanın
Deneyimsiz programcılarda görülen yanlış bir pratik: program lojiğini istisnalarla kurmak
def main
file = ARGV.first
content = File.read file
...
rescue Errno::ENOENT
abort "File not found: #{file}"
end
main
Böyle yapmayın!
def main
file = ARGV.first
abort "File not found: #{file}" unless File.exist? file
content = File.read file
...
end
main
Çok Biçimlilik
Çok Biçimlilik
[Circle.new(3, 5, 2), Square.new(9, 4, 1)].each do |shape|
puts "#{shape} şeklinin alanı: #{shape.area}"
end
Bir isim → Çok biçim
Bir isim:
area
Çok biçim:
shape
nesneleriarea
mesajına farklı şekilde cevap veriyor
Diğer bir çok biçimlilik örneği
var these = [19, "19"];
var those = [];
for (var i = 0; i < these.length; i++)
those.push(these[i] + 1);
// those = [20, "191"]
JavaScript'te
+
operatörü çokbiçimli davranıyorDizideki güncel nesne eğer:
Tamsayı ise toplama yapıyor
Dizgi ise tamsayıyı dizgiye çevirip ekleme yapıyor
Çok Biçimlilik'te
Sadece davranış ismiyle ilgileniyoruz →
area
Davranışı sergileyen nesnelere ortak bir isim veriyoruz →
shape
(şekil)Nesnenin kimliğini ayrıntılı bilmemiz gerekmiyor →
Circle
,Square
shapes = [ Square.new(3), Rectangular.new(width: 1, height: 3, position: Point.new(3, 5)), Circle.new(2) ]
shapes.each do |shape|
case shape
when Square then area = shape.side * shape.side
when Rectangular then area = shape.width * shape.height
when Circle then area = shape.radius * shape.radius * Math::PI
else raise("Tanınmayan şekil: #{shape}")
end
puts area
end
shapes = [ Square.new(3), Rectangular.new(width: 1, height: 3, position: Point.new(3, 5)), Circle.new(2) ]
shapes.each do |shape|
puts shape.area
end
Dinamik Dağıtım
Bir kargo merkezi ve kargo paketleri
Kargo paketleri nasıl dağıtılır?
Paketin hangi yolla (hava, kara, deniz) gönderileceğine karar vermek lazım
Alıcı adrese göre yöntem değişir
Cargo Dispatch
Alıcı adrese bak
Ulaşım yöntemini tayin et
Tek isim →
send
, çok biçimli davranış → farklı ulaşım yöntemleri
Şekil nesneleri arasında dolaşıyor ve
area
metodunu çalıştırıyoruzarea
'nın hangi gerçeklemesi çalıştırılacak?İlgili nesnenin sınıfı neyse o sınıfta tanımlı metod çalıştırılacak
Ama ilgili nesnenin sınıfını bilmemiz gerekmiyor
Uygun
area
gerçeklemesine çalışma zamanında karar veriliyor
Kargo paketi nasıl gönderilecek?
send
metoduŞekil nesnesinin alanı nasıl hesaplanacak?
area
metoduKarar anında bir tür dağıtım (dispatch) yapılıyor
Bu karar çalışma zamanında veriliyor
Dinamik dağıtım → dynamic dispatch
Arayüz
Vana/vida → açma/kapama
Arabalar → direksiyon, vites sistemi
Telefon → kulaklık/mikrofon, tuş takımı
USB standartı → Aygıtlar, USB soketleri
İç yapısı (gerçeklemesi) farklı olabilen nesnelerle haberleşmemizi sağlıyor
Bir tür sözleşme
Vanayı saat yönünün tersine çevirerek akışı kes
Aracın kalkışında debriyaj, 1'nci vites, gaz
Telefonda arama yapmak için 4 + 7 haneli numarayı tuşla/çevir
USB aygıtı kullanmak için aygıtı USB soketine tak
Nesneleri arayüzler üzerinden kullanıyoruz
Arayüzler aynı, gerçeklemedeki farklı olabilir
Arayüz ve Çok Biçimlilik
Arayüz ve Çok biçimlilik birbiriyle yakından ilintili
Çok biçimlilik → bir isim, çok biçim
İsim → arayüzde tanımlanan bir sözleşme maddesi
Biçim → ilgili arayüzün (o sınıf için) gerçeklemesi
Nesne İlişkileri
Miras Alma
UML Diyagramları
Sınıf ilişkilerini UML diyagramlarıyla gösteriyoruz
Bir modelleme aracı
Unified Modelling Language
Tasarım sırasında yararlı
- "Miras Alma" ilişkisi ebeveyn sınıfa doğru içi boş bir ok
is-a
"Miras Alma" bir is-a
ilişkisi kurar
Car
is a
Vehicle
: Araba bir Araçtır
Bu ilişkinin doğal olması lazım
Aksi halde hatalı bir soyutlama yapmış olursunuz
Hatalı soyutlamanın bedeli?
Karmaşık ve kırılgan kod
Yapboz'da yerine oturmayan parçalar
Miras Alma (Hatalı Soyutlama)
class Vehicle
# Araç'la ilgili tüm ortak akıl burada...
def start_engine
puts "başla"
end
def stop_engine
puts "dur"
end
end
class Car < Vehicle
# Hafta sonu turu
def sunday_drive
start_engine
# Gez dolaş ve dön...
stop_engine
end
end
def main
car = Car.new
car.sunday_drive
end
main
Car
is a
Vehicle
: Araba bir Araçtır
Yukarıdaki cümle kulağa doğru geliyor; fakat...
İsimlendirmelere aldanmayın, modele bakın
"Araç" nasıl modellenmiş?
Vehicle
sınıfıylaVehicle
? "Motoru olan bir nesne" üretir
Her aracın (vasıtanın) bir motoru var mıdır?
Bisiklet?
Planör?
Kayık?
Bize başka bir soyutlama lazım
Her aracın motoru olmayabilir
"Araba motoru olan bir araçtır"
Kompozisyon
- "Kompozisyon" ilişkisi kompoze edilen sınıfa doğru içi dolu bir ok
has-a
"Kompozisyon" bir has-a
ilişkisi kurar
Car
is a
Vehicle
, thathas an
Engine
"Araba motora sahip bir Araçtır"
Bakın bu farklı bir model
class Engine
# Motor'la ilgili tüm ortak akıl burada...
def start
puts "başla"
end
def stop
puts "dur"
end
end
class Car
def initialize
@engine = Engine.new
end
# Hafta sonu turu
def sunday_drive
@engine.start
# Gez dolaş ve dön...
@engine.stop
end
end
def main
car = Car.new
car.sunday_drive
end
main
Kompozisyon veya Miras Alma?
Mümkün olan her yerde "Kompozisyon"u, "Miras Alma"ya tercih edin.
Bir modellemenin eşiğindesiniz
Hemen
is-a
vehas-a
ilişkilerini tayin edinModel'e alternatif açılardan bakın
is-a
ilişkisi çok doğal olmalıDoğal değilse
has-a
ilişkisini arayınDoğallık şüphesi varsa
has-a
Bazen "Kompozisyon" ve "Miras Alma" birlikte kullanılabilir
class Engine
# Motor'la ilgili tüm ortak akıl burada...
def start
puts "başla"
end
def stop
puts "dur"
end
end
class GasolineEngine < Engine
# Benzinli Motor
end
class DieselEngine < Engine
# Dizel Motor
end
class Car
def initialize
@engine = GasolineEngine.new
end
# Hafta sonu turu
def sunday_drive
puts "Motor tipi: #{@engine.class}"
@engine.start
# Gez dolaş ve dön...
@engine.stop
end
# Dizel'e çevir
def switch_to_diesel
@engine = DieselEngine.new
end
end
def main
car = Car.new
car.sunday_drive
car.switch_to_diesel
car.sunday_drive
end
main
Delegasyon
"Kompozisyon"lu modelde motorun çalışma aklı nerede?
Motorda ama bir kısmı Araba'ya sızmış gibi:
car.start
Araba motorun çalışmasına bu kadar yakın olmamalı
Araba modelinin müşterisi motorun çalışmasıyla ilgilenmez
Ne yapmalı?
Araba içinde motorun çalışmasıyla ilgili tüm işler motora devredilmeli
Car
sınıfında motorla ilgili tüm işlerEngine
sınıfına bırakılmalıEngine
sınıfı "motor işleri"ni temsil ediyor"Motor"un delegasyonu
class Engine
# Motor'la ilgili tüm ortak akıl burada...
def start
puts "başla"
end
def stop
puts "dur"
end
end
class GasolineEngine < Engine
# Benzinli Motor
end
class DieselEngine < Engine
# Dizel Motor
end
class Car
def initialize
@engine = GasolineEngine.new
end
def sunday_drive
puts "Motor tipi: #{@engine.class}"
start
# Cruise out into the country and return...
stop
end
def switch_to_diesel
@engine = DieselEngine.new
end
def start
@engine.start
end
def stop
@engine.stop
end
end
def main
car = Car.new
car.sunday_drive
car.switch_to_diesel
car.sunday_drive
end
main
Bu gerçeklemede bir sorun var
Kod DRY değil
start
vestop
metodları bu eylemleri motor nesnesine gönderiyorHer delegasyonda böyle sarmalayıcı metodlar yazmak zorunda mıyız?
"Şu isimdeki metodları şu nesneye gönder" gibi bir kolaylık?
Delegasyon - Forwardable
modülü
require 'forwardable'
# Motor
class Engine
# Motor'la ilgili tüm ortak akıl burada...
def start
puts 'başla'
end
def stop
puts 'dur'
end
end
class GasolineEngine < Engine
# Benzinli Motor
end
class DieselEngine < Engine
# Dizel Motor
end
# Araba
class Car
extend Forwardable
def_delegators :@engine, :start, :stop
def initialize
@engine = GasolineEngine.new
end
def sunday_drive
puts "Motor tipi: #{@engine.class}"
start
# Cruise out into the country and return...
stop
end
def switch_to_diesel
@engine = DieselEngine.new
end
end
def main
car = Car.new
car.sunday_drive
car.switch_to_diesel
car.sunday_drive
end
main
Örnek: Üretilen nesneleri sayı
class Rectangular
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
end
end
def main
rectangulars = []
rectangulars << Rectangular.new 1, 2
rectangulars << Rectangular.new 3, 5
rectangulars << Rectangular.new 4, 9
puts rectangulars.size
end
main
Nasıl ve nerede üretildiğine bakılmaksızın inşa edilen dikdörtgenlerin sayısını nasıl takip ederiz?
Nasıl ve nerede üretildiğine bakılmaksızın...
İlk örnekte nesne sayını
main
metodunda bir dizi üzerinden öğreniyoruzÇeşmenin başı?
initialize
metodu?
class Rectangular
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@count += 1 # ???
end
end
Bu yaklaşımın sorunları?
@count
bir nesne niteliği, nesneye özgüHalbuki bize tüm dikdörtgen nesneleri için kullanılacak bir sayaç lazım, bir nesneye özgü olmamalı
Ayrıca
@count
niteliği nasıl ilklenecek?Her "construction"da eski değerini nasıl koruyacak?
Nesne düzeyinde değil sınıf düzeyinde anlamlı bir sayaç kullansak?
Sınıf değişkenleri
class Rectangular
@@count = 0
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@@count += 1
end
end
def main
rectangulars = []
rectangulars << Rectangular.new 1, 2
rectangulars << Rectangular.new 3, 5
rectangulars << Rectangular.new 4, 9
puts Rectangular.@@count # ???
end
main
Sınıf değişkeni
@@
ile başlıyorSınıf içinde (nesne metotları dahil) her yerden erişilebiliyor
Ama dışarıdan erişilemiyor (nesne nitelikleri gibi)
Özel bir erişici yazmamız lazım
class Rectangular
@@count = 0
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
@@count += 1
end
def self.count
@@count
end
end
def main
rectangulars = []
rectangulars << Rectangular.new 1, 2
rectangulars << Rectangular.new 3, 5
rectangulars << Rectangular.new 4, 9
puts Rectangular.count # ???
end
main
def self.count
@@count
end
@@count
için okuyucuBenzer şekilde yazıcı da yazılabilir
Sınıf değişkenleri için bunları yazmanın daha kısa bir yolu yok
Neden? Çok sık kullanılmıyor mu?
class Square < Rectangular
attr_reader :length
def initialize(length)
@length = length
super(length, length)
end
end
def main
rectangulars = []
rectangulars << Rectangular.new 1, 2
rectangulars << Rectangular.new 3, 5
rectangulars << Rectangular.new 4, 9
puts Rectangular.count
squares = []
squares << Square.new 1
squares << Square.new 2
puts Square.count
puts Rectangular.count
end
main
Miras almada sorun yok, sınıf değişkeni çocuk sınıflara başarıyla iletiliyor
Fakat önemli bir sorun var
@@count
tüm bir soyağacındaki doğumları takip ediyor; ata ve çocuk sınıflar
"Class variables considered harmful"
Sınıf değişkenleri kapsamı tanımlandığı sınıf ve mirasçıları olan global bir değişken gibi davranıyor
Global değişkenleri sevmiyoruz, neden?
Sadece tanımlandığı sınıfa özgü bir nitelik lazım
Bu nedenle sınıf değişkenleri sık kullanılan bir şey değil ve olmamalı
"Nesneye özgü"lük
Nesne nitelikleri sadece bir nesneye özgüdür; nesneler arasında paylaşılmaz
Sınıfa özgü bir nitelik nasıl tanımlanır?
Sınıf da eğer bir nesne ise bu yapılabilir?
Ruby'de sınıflar da birer nesnedir
Kulağa tuhaf geliyor
"Nesneye özgü"lükten yararlanmamız için anlamlı olacak bir cümle
Ruby'de "nesneye özgü" bir nitelik daima @
ön ekiyle belirtilir
Sınıf nesnesine özgü bir nitelik de
@
ile başlamalıFakat nesne metotları içinde kullanılan
@
nitelikleri nesneye özgü
Bu durumda
Sınıf nesnesi niteliği nerede ilklenebilir
İpucu:
@
niteliklerini ilk seviye sınıf gövdesinde kullanmayı denediniz mi?Sınıf nesnesi niteliklerine nasıl erişilir?
self
'in öyküsü
class SomeClass
p self
def instance_method
p self
end
end
def main
some = SomeClass.new
some.instance_method
end
main
class BirSınıf
BU BÖLGE SINIF NESNESİNİN
def herhangi_bir_nesne_metodu
BU BÖLGE SINIFTAN İNŞA EDİLEN NESNENİN
end
end
Sınıf nesnesi nitelikleri
class Rectangular
@count = 0
attr_reader :width, :height
def initialize(width, height)
@width = width
@height = height
Rectangular.count += 1 # @count += 1 olmaz değil mi?
end
def self.count
@count
end
def self.count=(value)
@count = value
end
end
def main
rectangulars = []
rectangulars << Rectangular.new 1, 2
rectangulars << Rectangular.new 3, 5
rectangulars << Rectangular.new 4, 9
puts Rectangular.count # ???
end
main
Deneme yapalım
class Square < Rectangular
attr_reader :length
def initialize(length)
@length = length
super(length, length)
end
end
def main
rectangulars = []
rectangulars << Rectangular.new 1, 2
rectangulars << Rectangular.new 3, 5
rectangulars << Rectangular.new 4, 9
puts Rectangular.count
squares = []
squares << Square.new 1
squares << Square.new 2
puts Square.count
puts Rectangular.count
end
main
Sınıf nesnesi niteliği için erişiciler (getter/setter)
@count = 0
def self.count
@count
end
def self.count=(value)
@count = value
end
Bunun bir kısayolu yok mu?
Nesne niteliklerinde
attr_*
metotlarını kullanıyorduk, bunun gibi bir şeyattr_*
metotlarını sınıf nesnesi nitelikleri için kullanamayız değil mi?Bu metotlar nesneye özgü nitelik erişicileri tanımlıyor;
self
'i takip edin
Sınıf nesnesi nerede?
@
ile başlayan sınıf nesnesi nitelikleri için sınıfın birinci seviye gövdesindeFakat bu durum sınıf düzeyinde çalışan
attr_*
metotları için geçerli değilSınıf düzeyinde çalışan bu metotlar nesne metotu yazıyor, sınıf nesnesi metotu değil
Sınıf nesnesi üzerinde etki gösterecek bir yer olmalı; içte bir yer
Hatırladınız mı?
class Rectangular
@count = 0
class << self
def count
@count
end
def count=(value)
@count = value
end
end
end
class Rectangular
@count = 0
class << self
attr_accessor :count
end
end
Sınıfı "adeta" yarıp en iç kısmına erişiyoruz
Artık sınıf nesnesi içindeyiz
Buna "eigenclass" diyoruz, öz sınıf
attr_*
metotlarının yazdığı nesne metotları artık sınıf nesnesine ait, nesneye değil
Sınıf düzeyinde anlamlı nitelikleri sınıf değişkenleri yerine sınıf nesnesi nitelikleriyle temsil edin
Sınıf sabitleri
Sınıf değişkenleri
Sınıf nesnesi nitelikleri
Nesne nitelikleri
Şimdi toparlayalım, bir deneme yapacağız
Tüm bu ögelerin kalıtımdan nasıl etkilendiğine bakacağız
Yardımcı bir metot tanımlayarak başlayalım
# Yardımcı
def dump(p)
puts "#{p}: #{eval(p, TOPLEVEL_BINDING) || 'boş'}"
end
puts "A sınıfı tanımlanıyor"
class A
attr :instance_variable
CONSTANT = "#{self}_CONSTANT"
@@class_variable = "#{self}_class_variable"
@class_instance_variable = "#{self}_class_instance_variable"
def initialize
@instance_variable = "#{self}_instance_variable"
end
class << self
attr :class_instance_variable
def class_variable
@@class_variable
end
end
end
puts "B < A sınıfı tanımlanıyor (değişiklik yapmadan)"
class B < A
end
puts "C < A sınıfı tanımlanıyor (değişiklik yaparak)"
class C < A
CONSTANT = "#{self}_CONSTANT"
@@class_variable = "#{self}_class_variable"
@class_instance_variable = "#{self}_class_instance_variable"
end
puts "A sınıfından a nesnesi oluşturuluyor"
a = A.new
puts "B sınıfından b nesnesi oluşturuluyor"
b = B.new
puts "C sınıfından c nesnesi oluşturuluyor"
c = C.new
puts
dump 'A'
dump 'A::CONSTANT'
dump 'A.class_variable'
dump 'A.class_instance_variable'
dump 'a.instance_variable'
puts
dump 'B'
dump 'B::CONSTANT'
dump 'B.class_variable'
dump 'B.class_instance_variable'
dump 'b.instance_variable'
puts
dump 'C'
dump 'C::CONSTANT'
dump 'C.class_variable'
dump 'C.class_instance_variable'
dump 'c.instance_variable'