object orıented programmıng temelleri

Transkript

object orıented programmıng temelleri
OBJECT ORIENTED PROGRAMMING TEMELLERİ
Geçenlerde kitaplığımı kurcalarken eskimiş bir not defteri buldum, yıllar önce (sanırım 2008-2010) OOP
öğrenirken aldığım kısa notlarla karşılaştım, yazarak öğrenme metodunu kullanan biri olarak o zaman aldığım
notlar nesne yönelimli programlama temelleri konusunda şimdi hâlâ geçerliliğini koruyor. Notlarımı
derleyerek, imlâ hatalarından arındırarak ve bu günün yansımalarından birkaç not ekleyerek burada
paylaşıyorum, umarım bu notlar faydalı olur.
Yazılım kitaplarında nesne yönelimli programlama başlığı altında bu konu anlatılıp, ‘nesne’ kavramı
açıklanırken, Okul-Sınıf-Öğrenci örneği verilir. Öğrenci bir nesne ve öğrenciye erişmek için, önce okula sonra
sınıfa sonra da öğrenciye (nesneye) erişilir. Daha önce, düşük seviyeli diller ile kod yazmayan, ilk öğrendiği dil
C# gibi temeli nesneye dayanan bir dil ile uğraşan birisi, bu örnek sonrasında “- e başka türlü nasıl olabilirdi
ki zaten?” gibi bir soruyu aklına getirebilir.
Nesne yönelimli olmayan bir dil yazmış olan geliştiriciler, nesne yönelimli programlamanın ne ifade ettiği,
sunduğu kolaylıkları, neden ihtiyaç duyulduğu gibi konuları kavrama konusunda avantajlıdırlar, bu nedenle
kavranması açısından biraz geriye gidip nasıl aşamalardan geçildiğine değinmekte fayda var.
OOP öncesinde işler nasıl yürüyordu?
Programlama dillerinin evrimi derin bir konu, bu nedenle 0-1’lere, instruction pointer’lara kadar derinlere
inmemize gerek yok. Kısaca özetleyecek olursak programlama dili kavramının ortaya çıktığı uzun yıllar
öncesinden yana bu evrimi; doğrusal programlama, yapısal programlama ve nesne tabanlı programlama
olarak üç başlık altında inceleyebiliriz.
Doğrusal programlama, alt alta yazılan ve çoğu zaman aynı satırlardan oluşan, start, goto, end gibi ifadelerin
kullanıldığı, program büyüdükçe karmaşıklaşan ve geliştirilmesi yeniden yazılması kadar bir zaman gerektiren
bir yapısı olduğundan yapısal programlama kavramı geliştirilmiştir. Yapısal programlama da kodlar
işlevselliklerine göre mantıksal olarak parçalara bölünebilirler ve bu sayede uzun ve karmaşık programların
küçük parçalar haline getirilip yazılmasını mümkün kılmaktadır. Bu yapı üzerinde çalışmak için metotlar
kullanılırken nesneye yönelimli programlamada ise sınıflar içinde metot yapısını veri yapısı ile ilişkilendirilir,
nesneler her biri kendi içlerinde veri işleyebilir ve diğer nesneler ile çift yönlü veri transferinde bulunabilir,
yapısal programlamada ise programlar sadece bir komut dizisi olarak görülebilmektedir. Bu nesne yönelimli
programlamayı diğer yapılardan öne çıkaran en önemli özelliklerden biridir. Yüz binlerce satır kod nesne
yönelimli bir dil ile yazıldığında yönetilmesi daha kolay olacaktır, çünkü kendi içerisinde yüzlerce nesneye
bölünecektir. Amaç geliştirilen programın mümkün olduğu kadar küçük ve anlamlı parçalara bölüp, her
parçayı bir türde nesne haline getirip daha sonra bu nesneleri gerektiği yerde kullanmaktır. Özet olarak oop
reusable kodların yazılması, bu da yazılan programın esnetilebilmesi ve geliştirilebilmesinde büyük kolaylık
sağlamaktadır.
C++ programlama dilinin yazarı Bjarne Stroustrup’a göre bir dilin kendisini nesne yönelimli olarak
tanımlayabilmesi için, üç kavramı desteklemesi gerekir: nesneler, sınıflar ve kalıtım. Ancak nesne yönelimli
diller; Encapsulation (sarmalama), Inheritance (Miras Alma) ve Polymorphism (Çok Biçimlilik) üçlüsü üzerine
oturtulmuş diller olarak düşünülür. Tanımlamadaki bu değişimin nedeni, geçen yıllar boyunca encapsulation
ve polymorphism’in class ve inheritance gibi nesne yönelimli sistemler oluşturmanın bir iç parçası olduğunun
farkına varılmasıdır.
Sahi, başından beri nesne yönelimli programlama demişken nedir bu Nesne?
Her şey nesnedir! Bu felsefi bir yaklaşım gibi oldu kabul ediyorum, bunu biraz açıklayalım.
Her şey bir nesnedir demiştim, etrafınıza bir bakın, ilk gözünüze çarpan şey nedir? Ben bu makaleyi siyah 0.5
uç kullanan bir kalemle yazıyorum, bu kalemin bir ağırlığı, bir rengi, bir markası var, bunlar bu kelemin
özellikleri. Aynı zamanda bu kalemin tepesindeki metal kapağa basıp içindeki ucu çıkartabiliyorum; bu da
davranışı. Nesneler iki temel bileşenden oluşur. Bunlar özellik (property) ve davranış (behavior)’dır. Burada
kalem bir nesnedir.
“İşe .net tarafından bakınca, C# nesne yönelimli bir dildir ve bu dili kullananlar; “tanımladığımız her şey
System.Object’ten türediğine göre her şey bir object(nesne)dir” diyebilir. .net tarafından bakılınca bu manaya
da gelebilir ama bu sadece bir kavramdır, yani nesnenin tanımı değildir .”
Bu konuştuklarımızın kod karşılığını görelim. Nesneleri yazmak için önce bir şablon(kalıp) oluşturur, daha
sonra bu şablondan istediğimiz kadar nesneyi çıkartabiliriz. Bu şablonlara class, bu şablonlardan oluşturulan
nesnelere’de object adı verilir. Class ile object arasındaki ilişki bu şekildedir.
class Kalem
{
string Renk;
string Marka;
string Uc;
int Uzunluk;
int Agirlik;
}
void Bas()
{
// üzerine basılınca bir miktar uç çıkar
}
Yukarıdaki kod örneğinde görüldüğü gibi, sınıf nesneyi tanımlayan bir veri türü, nesne ise sınıftan
türetilebilen bir yapıdır. Nesneleri yazmak için önce bir şablon oluşturur, sonra bu şablondan istediğimiz
kadar nesneyi çıkartabiliriz.
Kalem k = new Kalem();
Yukarıdaki tek satırlık kodda new Kalem(); diyerek kalem sınıfının instance’ını almış yani Kalem classından
nesne türetmiş olduk. Peki, instance alınca ne oluyor? Burada Ram yapısına göz atmakta fayda var. Ram
kısaca, stack ve heap olan iki alandan oluşur. Stack, local variable’ların olduğu, heap ise class instance’larının
olduğu alandır.
Kalem sınıfını instance alıp, değer atamak gibi birkaç işlem yapıp, bu işlemlerin ram’e nasıl yansıdığını
göstermek için basit bir stack-heap schema hazırladım. (Bu dökümanın sonunda yer almaktadır)
Eğer linkteki pdf’i incelediyseniz gördüğümüz gibi Kalem sınıfı k ile tanımladık (Kalem k;) ama türetmedik;
türetmediğimiz için ram’de stack alanına olarak atandı, k değeri Kalem sınıfına ulaşmak için bir değerdir.
Kalem bilgisinin tutulabilmesi için Heap bölümünde tanımlanması (instance alınması) gereklidir. new Kalem()
diyerek instance alıp ramdeki değişikliğe baktığımızda kalem nesnesinin bir adres ile ilişkilendirildiğini
görüyoruz. Bu adres bizim için referans adresi, yani biz kalem nesnesini call ettiğimiz zaman bu adresten
çağırılır, steak alanında, biz yeni bir instance almadan, yeni bir kalem nesnesi oluşturmaz. Steak heap yapısı
detaylı bir konu; bu nedenle “Memory management” başlığı altında bu konuyu araştırmanızı öneririm.
Buraya kadar nesneleri tanımladık fakat tanımlanan nesne ile ilgili bir erişim sorunumuz var:
Acces Modifiers (Erişim Belirleyiciler)
Yazdığımız program içerisindeki classlara, bu classlar içindeki metodlara, değişkenlere nasıl ve ne zaman
erişeceğimizi belirten kurallar var, bunlara Acces Modifiers (Erişim Belirleyiciler) denir. Bu kurallar nesne
içerisindeki üyelerin erişim durumlarını belirler.
Bunlar : public, private, protected, internal, protected internal ’dır. Şimdi bunları inceleyelim.
public : Public olarak tanımlanan üye tüm erişimlere açıktır.
protected : Kendi üyesi olduğu (tanımlandığı) sınıflardan ve bu sınıftan türetilmiş sınıflardan erişilir.
private : Yalnızca üyesi olduğu sınıftan erişilir.
internal : Aynı program(assembly) içerisinden erişilir.
protected internal : Class’ın içinden ve ondan türetilen sınıfların içinden erişilir.
Nesneyi anlatırken yazdığımız Kalem sınıfı içerisindeki değişkenlerin bir erişim belirleyicisi olmadığını fark
etmişsinizdir, yazılan her hangi bir nesne için erişim belirlenmemiş ise, bu nesnenin erişimi default olarak
private‘dır. Aşağıdaki tabloda yapıların erişilebilirlik durumunu görebiliriz.
Yapı Adı
Default Erişilebilirlik
Struct
İnternal
Interface
Public
Enum
İnternal
Geçerli olabilen
erişilebilirlik
internal
public
private
protected
İnternal
Public
Private (nested struct)
Public
internal
Üyelerin erişebilirliği
Public
protected
internal
private
protected internal
Public
İnteral
private
Constructor (Yapıcı) Metot
Bir sınıftan bir nesne oluşturulduğu zaman, yani nesneyi ram’de inşa ederken başlangıç olarak o nesne ile
ilgili yapısını değiştirecek bazı farklılıklar olsun isteyebiliriz, bunun için constructor adı verilen özel bir
metodumuz var. Aslında bir nesneyi türetirken new anahtar sözcüğünü kullandığımızda default olarak bir
constructor oluşturmuş oluruz, eğer kendi constructor’ımızı oluşturmaz sak default olarak bu temel
alınacaktır. (variable initilializer) Constructor sınıf adı ile aynı adı taşır, dışarıdan parametre alabilirler, geriye
değer döndürmezler, overload yapılabilirler.
(overload nedir? gibi bir soru var ise kafanızda, yazının ilerleyen bölümlerinde öğreneceksiniz)
Şimdi Kalem sınıfında çalışarak bir constructor yazalım ve yazdığımız kodu yorumlayalım.
public class Kalem
{
public Kalem()
{
Marka = "Faber Castell";
}
public Kalem(string marka)
{
Marka = marka;
}
private
private
private
private
private
}
string Renk;
string Marka;
string Uc;
int Uzunluk;
int Agirlik;
private void Bas()
{
// üzerine basılınca biraz uç çıkar
}
Kod Yorumu: Kalem adlı sınıfımızda bir constructor oluşturuyoruz, constructor sınıf adı ile aynı adı taşır,
birden fazla constroctor olabilir, bu kod bloğunda iki tane constructors var, ilki default olarak markamızı
tanımlayan, “Faber Castell” değerini verendir, biz bu nesneyi üretirken, marka değişkenine otomatik olarak
“Faber Castell” değeri atanır. Nesneyi her ürettiğimiz yerde tekrar bu marka değişkenine değer atamak
zorunda kalmayız. İstisnai durumlarda ise overload yapılabilsin diye, ikinci construcor parametre alarak
marka değişkenine değer atanması sağlanır.
int, byte, char gibi değer tiplerinin de yapıcı yordamları bulunmaktadır, kod yazarken bu değerlere tiplerine ilk
değerlerini vermeden kullanmak istediğimizde hata alınır.
Destructor (Yıkıcı) Metot
Oluşturduğumuz nesnenin, bellekten silinmeden önceki gerçekleştireceği son işlemi gerçekleştiren
metoddur. Tilde (~) işareti ile başlar.
public class Kalem
{
~Kalem()
{
// bellekten silinmeden önceki gerçekleştirilecek işlemler
}
}
Encapsulation (Sarmalama)
Encapsulation adı verilen yapı, bir sınıf içerisindeki değişkenlere “kontrollü bir şekilde erişimi sağlamak /
denetlemek” için kullanılan bir yapıdır. Class içerisindeki değişken privete yapılırak dışarıdan direkt erişilmesi
engellenir, bu değişken içerisine değer atıp değer okumak için get ve set adı verilen metodlar kullanılır. Yani
direkt değişkene değil, bu değişkene erişmek (işlem yapmak) için bu metodlar ile çalışılır. set değeri alıp
değişkene atar, get’de değeri geri döndürür, tabii bu işlem sizin belirlediğiniz, olması gereken kurallar
çerçevesi içinde gerçekleşir. Basit bir örnek verecek olursak; kitap adında bir class’ınız var ve bu class’ın
içinde yazar adı, yayın evi, kitap sayfası, baskı yılı vb. property’ler mevcut. Bu property’lerden kitap sayfasına,
her kitabın 2’den fazla sayfası olmak zorunda olduğu için 2 sayısından az bir değer girdirmemeliyiz, buna
teşebbüs edildiğinde de hata döndürmeliyiz, bu nedenle sayfa sayısı propertysini encapsulete edelim.
(Property de nedir? En basit hali ile public int MyProperty { get; set; } şeklindeki yapıdır)
public class Kitap
{
public string yazarAdi { get; set; }
private int _sayfaSayisi;
public int sayfaSayisi
{
get { return _sayfaSayisi; }
set
{
if (value <= 2)
{
throw new Exception("Kitap Sayısı 2'den büyük olmalıdır.");
}
}
}
}
_sayfaSayisi = value;
public string yayınEviAdi { get; set; }
public DateTime basimTarihi { get; set; }
Kod Yorumu : Sayfa sayısı property’sini 2’den küçük ve 2 sayısına eşit olduğunda “Kitap Sayısı 2'den büyük
olmalıdır." Şeklinde bir hata dönecektir. Kitap class’ı içerisindeki SayfaSayisi propertysini encapsulate etmiş
olduk. Çok basit değil mi?
Not: c# dilinin v6 sürümünde auto property initilalizers özelliği gelerek property yapısı biraz daha
geliştirilmiştir
public string Key { get; } = "ABC123";
public int ID { get; set; } = 17;
set değerini kaldırarak Key propertysi sürekli “ABC123” değerini döndürecektir. Ve
readonly’dir.
Inheritance (Miras Alma, Kalıtım)
Miras kelimesini tanımı olarak, en basit haliyle birinden başka birine kalan varlık anlamına geldiğini biliyoruz,
bunun programlama tarafındaki anlamı ise; bir class içersindeki elemanların (property, metod) farklı nesneler
tarafından kullanılabilmesini sağlayan yapıdır.
Yine yayın evi örneğinden gidecek olursak; bir yayın evi sadece kitap değil, farklı türlerde baskılı ürün (aylık
dergi, broşür) yayınlayabilir, bunların ortak özelliklerinden biri de baskı tarihinin olmasıdır.
Yukarıda Kitap sınıfını düşünelim, yazarAdi, yayinEvi, sayfaSayisi ve basimTarihi olarak propertyleri var, kitap
classında basimTarihi olduğu gibi, benim brosur adlı bir nesnem olduğunda da basimTarihi’ne, dergi adlı bir
nesnem olduğunda da basimTarihi’ne ihtiyac duyacağım, bu nedenle türeteceğim nesnede bu property’leri
tek tek yazmaktansa, bir tane class’ım olsun(base class) , bu class’ın içinde benim oluşturacağım her class’ta
ihtiyacım olan propertyler olsun, ve ben oluşturduğum bu class’ları (derived class), bu sınıf ile yani base class
ile ilişkilendireyim. Bu yazdıklarımızı kod haline dönüştürüp yorumlayalım.
// Base Class’ımız
public class YayinEvi
{
public string yayınEviAdi { get; set; }
public DateTime basimTarihi { get; set; }
}
// Base Class’tan inheritance almış Kitap sınıfımız
public class Kitap : YayinEvi
{
public string yazarAdi { get; set; }
private int _sayfaSayisi;
public int SayfaSayisi
{
get { return _sayfaSayisi; }
set
{
if (value <= 1)
{
throw new Exception("Kitap Sayısı 1'den büyük olmalıdır.");
}
}
}
}
_sayfaSayisi = value;
Kitap ki = new Kitap();
ki.yayınEviAdi = "Everest Yayınları";
ki.basimTarihi = new DateTime(2009, 06, 01);
ki.SayfaSayisi = 367;
ki.yazarAdi = "Alev Alatlı";
ki.kitapAdi = "HoolyWood'u kapattığım gün";
Kod Yorumu: Kitap sınıfının içerisinde yayınEviAdi ve basimTarihi adlı propertyler olmamasına rağmen, bu
sınıfı türettiğimizde, bu propertylere erişebildiğimizi görüyoruz, çünkü Kitap sınıfı, YayınEvi sınıfından miras
aldığı için ve bu sınıfın elamanlarına erişebilmektedir.
class Kitap : YayinEvi
{
}
c# dilinde multiple inheritance yoktur, sadece bir sınıftan miras alabilirsiniz.
Abstract (Soyutlama)
Inheritance olayını öğrendiğimize göre, olayı biraz daha ileriye götürelim.
Soyut kavram, sözlük tanımı olarak; nesnelerin oluş tarzını ifade eden kavramlara soyut kavram denir. Bir
restorana gittiğimiz zaman, bizimle ilgilenen garsona “bize biraz besin getirebilir misin?” diyebilir miyiz?
Aklıselim bir insan isek cevabımız hayır olacaktır. Biraz besin yerine “zeytinyağlı enginar” dersek daha doğru
olacaktır, çünkü besin soyut bir kavramdır, zeytinyağlı enginar ise besin sınıfına ait bir nesnedir.
Bu örneğe kod tarafından yaklaşalım; kendisinden nesne üretmeye izin vermeyen, sadece miras alınabilen
sınıfları abstract class olarak tanımlayabiliriz. Abstract classın içerisine, gövdesi olan abstract bir metod
yazdığınızda ' cannot declare a body because it is marked abstract hatası alırsınız, çünkü abstract metodların
gövdesi olmaz. Yine bu metodu private olarak tanımladığınızda virtual or abstract members cannot be
private hatası alırsınız, yani hata metninden de anlaşılacağı üzere abstract metodlar private olamaz. Abstract
base class mantığındadır, sadece temel oluşturmak için kullanılır, yani bu sınıf inheritance alındıktan sonra
kullanılabilir, tek başına işlevi yoktur, bu nedenle en karakteristik özelliği kendisinden nesne üretmesine izin
vermez, abstract olmayan bir sınıfın içerisinde abstract bir elaman olamaz. Abstract bir sınıfı, miras alan bir
sınıf, bu miras aldığı abstract sınıfı içerisindeki metodu override eder. Kod tarafına göz atalım:
public abstract class KitapBase
{
public abstract int SayfaSayisi { get; set; }
}
// KitapBase sınıfını inheritance almış Kitap sınıfımız.
public class Kitap : KitapBase
{
private int _sayfasayisi;
public override int SayfaSayisi
{
get
{
return _sayfasayisi;
}
set
{
}
}
}
_sayfasayisi = value;
Kod yorumu: Kitap sınıfı, base class olan KitapBase sınıfını miras alıyor ve SayfaSayisi property’sini override
ediyor.
Polymorphism (Çok Biçimlilik)
Polymorphism; bir sınıftan miras alınan metodun, miras alındığı sınıftaki işlevi ile, miras alan sınıfta farklı
işlevlerle çalıştırılabilme özelliğidir. Bu işlemin nasıl olduğunu görmeden önce bilmemiz gereken bazı
kavramlar var:
virtual : Metodun türetilmiş bir sınıfta geçersiz kılınmasını sağlamak için kullanılır.
override : Inheritance alınan bir sınıftaki metodu tekrar yazarak, base class’takini ezmiş, yoksaymış yani
override etmiş oluruz. Bunun gerçekleşebilmesi için base class’taki metodun virtual olarak tanımlanması
gerekir, yani buna izin vermesi gerekir ki virtual’un tanımını bu şekilde yapmıştık.
overload : Türkçe karşılığı ile bir metodun aşırı yüklenmesidir, yani aynı isimde birden fazla metod farklı
parametreler alabilir, ya da almayabilir. Hatırlarsanız Constructor’ı anlatırken Kalem sınıfı örneği vermiştik,
Kalem sınıfında iki tane constuctor vardı, biri sadece sadece Marka Değişkenini set ediyor, diğeri de dışarıdan
parametre alıyordu. Yani iki farklı şekilde işleyişi vardı, yani overload edilebiliyordu.
base : türetilmiş sınıfın içerisinden, türetildiği sınıftaki base class’taki (public, internal, protected olarak
tanımlanmış) elemanlara erişmek için kullanırız. Özetle Base class’ı referans alır. (abstract olarak tanımlanmış
üyeler için kullanılamaz) base sınıfı bir üst sınıfı temsil eder. grandparent-parent-child gibi bir yapıyı
düşünelim, grandparent’ta tanımlanış bir metoda child üzerinden this ile erişemeyiz. Sadece bir üst sınıfa,
yani türetildiği sınıfa erişilebilir.
this : bu sözcüğün base’den farkı içinde bulunduğu nesneyi referans alır.
Bu anlattıklarımızı pekiştirmek için basit bir kod bloğu yazalım.
// Base classımız
class Base
{
public virtual void Yaz1()
{
Console.WriteLine("Yaz1 base olan sınıftan, virtual olarak geldim");
}
public virtual void Yaz2()
{
Console.WriteLine("Yaz2 base olan sınıftan, virtual olarak geldim");
}
}
public void Yaz()
{
this.Yaz1();
this.Yaz2();
}
// Base class’tan türetilmiş Print classımız
class Print : Base
{
public override void Yaz1()
{
Console.WriteLine("derived olan sınıftan, override olarak geldim");
}
}
------------------------------------------------------------------------private static void Main()
{
Print prt = new Print();
prt.Yaz();
Console.ReadLine();
}
Kodun çıktısı aşağıdaki gibi olacaktır:
derived olan sınıftan override olarak geldim
Yaz2 base olan sınıftan, virtual olarak geldim
Kod Yorumu: Kodun çıktısından da gördüğümüz gibi this sözcüğünün kullandık ve metodu çağırdık, var ise
türetmiş sınıftan override edilmiş olanı, yoksa temel sınıftan asıl halini getirdi.
Şimdi öğrendiğimiz bu yeni bilgiler dâhilinde polymorphisme dönelim.
Bir sınıftan miras alınan metodun, (bu temel bir sınıf olabilir), bu metodun temel sınıftaki işlevi ile, miras alan
sınıfta farklı işlevlerle çalıştırılabilme özelliğidir demiştik, şöyle bir senaryomuz olsun, çeşitli ürünler satan
online bir dükkanınız var, Türkiye’nin her yerine sabit bir tutar ile kargo gönderiyorsunuz, fakat sadece
taşıma esnasında kırılabilmesi muhtemel ürünlerinizi (cam vazo gibi) korumalı kargo kutusunda
gönderiyorsunuz, bu da kargo fiyatını farklılaştırıyor. Her ürün için farklı bir metod yazmak bir çözüm ama
OOP ilkelerine ters bir çözümdür, peki bunu polymorphism ile nasıl yaparız bunu kod üzerinde görelim.
// Base Classımız
public abstract class Transfer
{
public string KargoFirması { get; set; }
public string TeslimSuresi { get; set; }
}
public virtual double KargoUcreti()
{
return 10.00;
}
public class OzelUrun:Transfer
{
public string UrunAdi { get; set; }
}
public override double KargoUcreti()
{
return 15.00;
}
Kod Yorumu : OzelUrun adlı classımız, base class olan Transfer classından miras alıyor, buraya kadar her şey
anlaşılır durumda, fakat burada farklı bir durum var, iki class içinde de aynı isimli metod var biri virtual
diğeride override olarak tanımlanmış. Base classımızdaki KargoUcreti() metodu her ürün için geçerli olup
sadece özel ürünlerde farklı bir şekilde çalışması gerekiyordu. Bunun için baseclass’ımızdaki KargoUcreti()
metodunu override ederek içeriğini değiştirdik, böylece OzelUrun adlı classımızdaki KargoUcreti() metodu,
bizim tanımladığımız şekilde çalışacaktır, base classımızdaki metod normal ürünler için 10.00 değerini
döndürürken, biz base class’taki KargoUcreti() metodunu override ederek içeriğini değiştirdik ve bizim
belirlediğimiz 15.00 değerini döndürmesini sağladık. Dikkat ederseniz base classımızdaki KargoUcreti()
metodu virtual olarak tanımlanmıştır, bu tanımlama bu metodun override edilebileceğini gösterir, yani buna
izin verir, aksi takdirde override etmeniz mümkün değildir.
Interface (Arayüz)
Interface (arayüz ya da arabirim olarak Türkçeye çevirilmiş) bir sınıfın temelde hangi elemanlardan oluşmak
zorunda olduğunu belirleyen şablondur, inheritance kavramında base bir sınıfımız vardı ve bu sınıfı miras
alan derived class, base classtaki metodları, özellikleri vb. kullanabilirdi fakat bu zorunlu değildi. interface ise
bunu mecburi kılan bir yapıdır, yani içerisindeki tüm elemanlar implement edilmek zorundadır, aksi takdirde
class does not implement interface member InterfaceName’ şeklinde bir hata ile karşılaşırsınız. Bir class
birden fazla sınıftan miras alamaz, fakat interface’lerin multi inhertitance desteği vardır, yani bir class birden
fazla interface’den miras alabilir. interface içerisindeki metodlar default olarak abstract seviyesindedir ve
içerisindeki tüm elemanlar public olarak tanımlıdır, interface’i de soyut bir sınıf olarak düşünebiliriz. Az
önceki örnekten gidelim, online dükkanınızda sattığınız her ürün için bir kargo işlemi gerçekleştirmek
zorundasınız. Bunun için ürün özelliklerini içeren sınıfınız olduğu gibi, bu ürün sınıfının kargo bilgileri de
olmak zorunda olacağından bu urun sınıfı kargo bilgilerinin olduğu bir Interface’den implement olmak
zorundadır. Bu konuştuklarımızın kod karşılığı aşağıdaki gibidir.
interface IKargo
{
string KargoFirması { get; set; }
string TeslimSuresi { get; set; }
}
void KargoUcreti();
public class Urun : IKargo
{
public string UrunAdi { get; set; }
public double UrunFiyati { get; set; }
public string KargoFirması { get; set; }
public string TeslimSuresi { get; set; }
}
public void KargoUcreti()
{
throw new NotImplementedException();
}
Kod Yorumu: Interfaceler bir standart olarak i harfi ile başlar. interface içerisindeki tüm elemanların miras
alınan sınıf tarafından implement edilmek zorunda olduğunu söylemiştik. IKargo interface’i içerisindeki tüm
elemanların, onu miras alan Urun sınıfı içerisinde görüyoruz. Bir sınıf birden fazla interface’den miras
alabileceğini söylemiştik, bunun için araya bir virgül eklemek yaterli.
public class Urun : IKargo, IBaskaBirInterface, IBaskaBirInterfaceDaha, …
Bir sınıf hem bir class, hem de birden fazla interface ile ilişkilendirilebilir.
public class Urun : BaseClass, IBaskaBirInterface, IBaskaBirInterfaceDaha, …
Son Olarak Bazı Notlar :
Bu makaledeki örnekler c# dili baz alınarak hazırlandı, peki OOP sadece c# dili için geçerli bir kurallar kümesi
değil, içinde bulunduğumuz zamanda pek çok dilin OOP desteği mevcut. (php, ruby vb.) sadece syntax
farklılıkları var, bunları kısaca örnekleyecek olursak
C# inheritance -> class Kitap : YayinEvi
Php inheritance -> class Kitap extends YayinEvi
Java inheritance -> class Kitap extends YayinEvi
Ruby inheritance -> class GoodDog < Animal
Görüldüğü gibi, c# dilinde “:” miras alınacak sınıf “:” işareti ile belirtilmişken, java ve php’de extends ile,
ruby’de de “<” işareti ile belirtilmiştir. Çalışma mantığı aynıdır.
Bu makale, yazının başında da belirttiğim gibi benim notlarımdan oluşan,
for beginners seviyesinde bir makale; oop bir makalede özetlenmeyecek
kadar geniş bir konu, bu noktada okuduğum ve çok faydasını gördüğüm bir
kitabı tavsiye etmek istiyorum, Beginning Object-Oriented Programming
with C# - Jack Purdum. Kitabın kindle ve baskılı halini amazon’dan temin
edebilirsiniz.
Engin Tosun 2015 – http://engintosun.net
Makalenin yayındaki link adresi: http://engintosun.net/Article/15/Object-oriented-programming-Temelleri
(kaynak gösterilmek şartı ile kullanılabilir)

Benzer belgeler