Nesne Yönelimli Programlama — Object Oriented Programming

Emre Büşlü
Fiba Tech Lab
Published in
11 min readSep 4, 2023

Merhaba,

Günümüz programlama dillerinin çoğu nesne yönelimlidir. Bu yaklaşıma hâkim olmamız bize çok fazla fayda getirir. 4 temel kavramı vardır. Abstraction (Soyutlama), Inheritance (Kalıtım), Encapsulation (Kapsülleme) ve Polymorphism (Çok biçimlilik). Bu temel kavramları C# dilini kullanarak etraflıca açıklamaya çalışacağım. Kavramlar birbiriyle iç içe olduğu için yazıyı en az iki kere okumanızı tavsiye ederim. Örnek kodlardaki mantıktan ziyade nesne yönelimli programlamanın (object oriented programming, kısaca OOP) ana fikirlerine odaklanmanız faydalı olacaktır. Yazıya başlamadan evvel OOP’ye dair bir kaç kavramdan bahsetmek isterim. (OOP merkezinde olmayan hususlar için asgari düzeyde bilgi vereceğim.)

Software Entity (Yazılım Varlığı) : Metot, özellik, alan vs. gibi kavramlara genel olarak yazılım varlığı diye hitap edeceğiz.

Class (Sınıf) : Çeşitli yazılım varlıklarını içerisinde barındıran bir veri şablonudur. Verileri tutmak, okumak ve işlemek için tanımlı olan varlıklarını kullanır.

Field (Alan) : Sınıf içerisinde tanımlı değişkenlerdir.

Method (Metot) : Sınıf içerisinde tanımlanan, girdi alabilen, çıktı üretebilen kod bloklarıdır. Belirli amaçlara yönelik operasyonel işlemleri tanımlar.

Property (Özellik) : Programlama dillerinde sınıflara ait alanlar oluşturulur ve bu alanları okumak/yazmak için metotlar (getter/setter) yazılır. C# dilinde bu ihtiyacı kısa ve kolay yoldan gidermek için özellikler(property) vardır. Aşağıdaki örnekte Name bir alan iken, Surname bir özelliktir (her özellik aynı zamanda bir alandır). get ve set anahtar kelimeleri birer metodu temsil eder ve bu metotlara gövde yazılabilir.

Field vs Property

Instance (Nesne Yaratmak/Türetmek/Oluşturmak) : Bir sınıftan nesne yaratıldığında RAM’in heap (öbek) bölgesinde bir adres ayrılır ve bu alan (okumak/yazmak için) erişilebilir hâle gelir. Bu işleme instance almak denir.

Object (Nesne) : Nesne, sınıfın hayat bulmuş hâlidir diyebiliriz. Bir sınıfın instance’ı alındığında nesne oluşur.

Override (Ezmek/Çiğnemek) : Bir metodun gövdesinin (eski metot gövdesini geçersiz kılarak) yeniden yazılmasıdır.

Overload (Aşırı Yükleme) : Bir metodun parametre listesini (ve eğer istenirse gövdesini) farklılaştırarak yeni bir versiyonunun oluşturulmasıdır.

Person personObject = new Person();

Reference Type (Referans Tip) : Yukarıdaki satırı incelediğimizde değişken adımızın (personObject) solunda Person sınıf ismi var. Bu, değişkenimizin referans tipidir. Bu referans tipin ; (eşittirin sağına baktığımızda) bir Person nesnesi tuttuğunu (refere ettiğini) görüyoruz. (Her referans tip, kendisiyle aynı tipte bir nesneyi tutmak zorunda değildir. Bu bilgi şuan anlamsız gelmiş olabilir, soyutlama kavramında konuyu işleyeceğiz).

Interface (Arayüz) : Geleneksel interface’ler çeşitli yazılım varlıklarının bildirimlerini içerir. Uygulandıkları sınıflara bu varlıkların implemente edilmesini mecbur kılan kontratlardır. Aksi taktirde derleme zamanında hata verir. (C# 8.0'den itibaren interface metot bildirimlerine gövde yazılabiliyor.)

Abstract Class (Soyut Sınıf) : Bu sınıflarda somut (yürütülebilir), soyut (implemente edilmesi gereken), sanal (override edilmesi gereken) metotlar, alanlar ve özellikler bulunur. Erişim belirteçleri müsaade ediyorsa yazılım varlıkları kalıtım yoluyla aktarılabilir.

Interface ve abstract sınıfların nesnesi oluşturulamaz. Fakat uygulandıkları sınıfların nesnelerine referans olabilirler.

Scope (Kapsam) : Yazılım varlıklarının yaratıldıktan sonra ömrünü sürdürebildiği bölgeye denir. (Daha detaylı bilgi için ayriyeten araştırmanızı tavsiye ederim, zira mühim bir husustur.)

Parent Class (Üst Sınıf) : Kalıtım veren sınıfa denir (super class veya base class olarakta bahsedilir).

Child Class (Alt Sınıf) : Kalıtım alan sınıfa denir.

Aşağıda OOP’nin kavramlarından bahsedeceğiz. Fakat yazıyı yazarken şunu fark ettim. Kavramların faydalarından bahsederken tekrara düşüyorum. Burada OOP’nin faydalarından kabaca ve topluca bahsedeceğim ve detay başlıklarda tekrar etmeyeceğim.

Uygulamadaki işlevsellikler için modüler bir yapı sağlar. Bu modüler yapı esnekliği, genişletilebilirliği, okunabilirliği, anlaşılabilirliği, performansı ve yönetilebilirliği artırır. Generic ve hiyerarşik yapılar kurabilmemizi sağlar. Kod tekrarını önler. Güvenli kod yazmamızı sağlar. Geliştirme takımının hata yapma ihtimalini düşürür. Kod düzenlemesini/iyileştirmesini ve test edilebilirliği kolaylaştırır.

Inheritance (Kalıtım)

Inheritance

Kalıtım; sınıflara ait yazılım varlıklarının tamamının veya bir kısmının hiyerarşik olarak birbirine aktarılmasıdır. Bu varlıkların hangilerinin kalıtım yoluyla aktarılacağına erişim belirteçleri karar verir (Erişim belirteçlerine kapsülleme başlığında değineceğiz). Konuyla ilgili olduğu için base ve sealed anahtar kelimelerinden de bahsedeceğiz. Kalıtımı ; sınıf isminin sağına “ : “ operatörü ile birlikte kalıtım alınmak istenen sınıf adını yazarak uygularız.

Animal.cs

1 özellik ve 2 metottan oluşan Animal sınıfımız. Bu bizim en üst sınıfımız olacak. Meet metodu virtual olarak işaretlenmiş, yani override edilebilir durumda.

Dog.cs

Dog sınıfımızda 1 özellik 3 metodumuz var ve Animal sınıfından kalıtım alıyor (5. satır). Meet metodunu override ettik ve sealed olarak işaretledik. 19. satırda kullanılan Name özelliğini sınıf kapsamında göremiyoruz fakat Dog sınıfı bu özelliğe sahip ve kullanabildik. Bu özellik Animal sınıfından kalıtımla alındı.

Beagle.cs

sealed : Beagle sınıfı için sealed anahtar kelimesini kullandığımıza dikkat edin (5. satır). Bu anahtar kelimeyle işaretlenmiş sınıflar kalıtım veremez. Eğer üst sınıf olarak kullanılmaya çalışılırsa derleme zamanında hata verir. Benzer durum override edilmiş Meet metodu için de geçerlidir (Dog sınıfında sealed olarak işaretlendiğini hatırlayın). Uygulamalardaki gerekli gördüğümüz varlıkları bu şekilde işaretleyebiliriz. Geliştiricilere; en özel sınıf/metot olduğa dair ipucunu verir. Kalıtımı sınırlandırır ve istenmeyen genişletmeleri engeller.

base : Beagle sınıfına Breathing adında yeni bir metot tanımladık ve içerisinde base.Breath() çağrımını yaptık. base anahtar kelimesi üst sınıfı temsil eder. Animal sınıfı içerisindeki Breath metodunu 14. satırda yürütmüş olduk. 9. satırda Animal sınıfında bulunan Name özelliğini de bu şekilde çağırdık. İsimlendirmelerde herhangi bir çakışma olmadığı için base anahtar kelimesini kullanmasak da sorun çıkartmazdı. (Dog sınıfının 19. satırı) Fakat açıkça yazmak daha iyidir.

Sınıflarımızı tanıttık. Şimdi olan bitene göz atalım.

Program.cs

9–12 satırlar arasında kalıtıma dair söyleyeceğimiz bir husus yok. 16. satırda Dog sınıfımıza ait nesneyi yaratıyoruz. Breath metoduna ve Name özelliğine erişiyoruz. Bu varlıklar Animal sınıfından alınan kalıtım ile kazanıldı. Meet metodu da aynı şekilde, fakat bu metodu override ettiğimizi hatırlayın. Çıktıda metnin değiştiğini görüyoruz. Age, GetAge ve Bark metotları Dog sınıfına aitti ve kullandık. Beagle sınıfına bakalım. Breath, Meet metotları ve Name özelliği Dog sınıfından kalıtımla alındı. (Dog sınıfına da Animal sınıfından kalıtımla geçmişti. Hiyerarşik yapıya dikkat edin.) GetAge, Bark metodu ve Age özelliği Dog sınıfından kalıtımla alındı. Play ve Breathing metotları Beagle sınıfının kendisine ait. Örneği daha kompleks hale getirebiliriz fakat kalıtımın anafikrine odaklanalım. Son olarak bir sınıfın birden fazla üst sınıfı olamayacağı bilgisini verelim.

Encapsulation (Kapsülleme)

Sınıflar yazılım varlıklarına sahiptir ve bu varlıkların erişim düzeylerini yönetmelidir. Yazılım varlıklarını bir araya getirip şeffaf bir kapsülün içine koyduğumuzu düşünün. Kapsül için şeffaf benzetmesi yapmamızın sebebi; içerisindeki varlıklara olan erişimi denetleyebiliyor olmamız. Bazılarına erişilememesi gerekirken, bazıları ise çeşitli durumlarda erişilebilir olmalıdır. Bunu erişim belirteçleri (access modifier) ile sağlarız. Nesnenin içerisinde bulunan varlıkları asgari düzeyde görmek isteriz. Müdahil olmamamız veya dışarıdan kullanılmaması gereken varlıklara erişemiyor olmamız bize fayda sağlar. Nesnenin sağladığı işlevselliği çeşitli metotlarla dışarıya sunarız ve kapsülleme sayesinde sınıf içerisindeki düzenlemelerimizin bu nesneyi kullanan diğer sınıfları etkilememesini bekleriz.

Yazdığımız sınıfların metot ve özelliklerin erişim belirteçleri olabildiğince özelden(private) genele(public) doğru ayarlanmalıdır. Erişim belirteçleri konusunda cömert olmamalı, ihtiyaca göre hareket etmeliyiz. (Bütün varlıklara public işaretleyebilirsiniz, teknik olarak mümkündür ve çalışır. Fakat proje büyüdükçe bu durumun bize getireceği karmaşıklığı hayal edin.) Örneklere geçelim.

Önce proje yapımızı genel hatlarıyla aşağıda vermek istiyorum. 2 adet projemiz olacak. Person sınıfımızı Encapsulation_2 projemizde kullanabilmek için bağımlılık verdik.

Project struct
Project Struct

Person sınıfını aşağıda görüyoruz. 6 adet özellik ve 1 adet metodumuz var. C# diline ait bütün erişim belirteçlerini kullandık. Hepsini tek tek açıklayacağız.

Person.cs

3. satırda Person sınıfının public olarak işaretlendiğine dikkat edin. Person sınıfını 2 farklı projedeki sınıflara (Student ve Employee) üst sınıf olarak tanımlayacağız. Ve bu varlıkların erişilebilirliklerini gözlemleyeceğiz. Öncesinde bu belirteçlerle alakalı gayet net bir tabloyu paylaşmak istiyorum.

Access modifiers
Access modifiers (Erişim belirteçleri)

Tanımlanan varlıklar tanımlandığı kapsamda erişilebilir durumdadır. Tablonun birinci satırına dikkat edin. Aşağıdaki örneklerde bu bilgiyi tekrar etmeyeceğiz.

this : Bu anahtar kelime sınıf tanımını temsil eder. Sınıf içerisinden bir varlığa erişmek istediğimizde “this.x” şeklinde bir çağrım yapabiliriz. Değişken isimlerinin çakıştığı durumda bu kullanım zaruri iken genellikle opsiyoneldir. Fakat kullanmak anlaşılabilirliği yükselttiği için tavsiye edilir.

Student.cs

3. satırda Student sınıfına erişim belirteci yazmadık. Sınıflar varsayılan olarak internal erişim belirtecine sahiptir (Açıkça internal yazabilirdik). Sınıflar internal ve public haricinde erişim belirtecine sahip olamazlar.

private : Person sınıfındaki Id özelliği private tanımlanmış. Bu erişim belirteci ; o özelliğin sadece bulunduğu kapsamda erişilebilir olduğu anlamına gelir. Bulunduğu kapsam dışında herhangi bir yerden erişilemez. En özel belirteçtir. IdentityNo özelliğine erişim belirteci yazmadık. Özelliklerin varsayılan belirteci private’tır. Student sınıfında bu iki özelliğe erişemediğimizi anlamlı bir mesajla görüyoruz. Bu özelliklerin kalıtım yoluyla aktarılması, sahip oldukları erişim belirteçleri sayesinde engellenmiş oldu.

protected : getPersonInfo metodu protected olarak işaretlenmiş. Bu belirtece sahip özellikler sadece kalıtım verilen sınıflar içerisinden erişilebilir olur (kalıtım alan sınıfa ait nesneden dahi erişilemez). Student sınıfında bu metodu çağırabildik.

Program.cs

Encapsulation projesinin Program sınıfı içerisinde Person ve Student nesneleri yaratıp nesnelere ait tüm varlıklara erişmeye çalıştık. Derleme zamanında hata veren varlıkları görüyorsunuz. Bu durumun sebebi sahip oldukları erişim belirteçleridir. getPersonInfo metoduna Student sınıfından erişebilirken bu sınıfa ait nesneden erişemiyoruz.

internal : Phone özelliğine şu ana kadar her yerden erişebildiğimizi görüyorsunuz. internal, mevcut proje (Encapsulation) içerisindeki herhangi bir yerden erişilebilir anlamına gelir (public gibi davranır). Nitekim internal olmayan projelerden erişilemediğini göstermek için Encapsulation_2 projesine ihtiyacımız oldu.

public : Bu belirteç adından da belli olacağı gibi herhangi bir kısıtlama olmadan her yerden erişilebilir anlamına gelir. Name özelliğini tüm sınıflarda gözlemleyebilirsiniz. En genel belirteçtir. Bu özelliğin get ve set metotlarına gövdeler yazdık. Name özelliğine erişildiği durumda bir kontrol koyduk. 2 karakter uzunluğundan daha kısa bir değer set edildiğinde hata fırlatıyoruz. Kapsülleme kavramı erişim düzeylerini yönettiği gibi, sağlanan erişimin kontrolünü de yönetebilir.

Aşağıdaki 2 sınıf (Employee ve Program) Encapsulation_2 projesine ait (Person sınıfını kullanabilmek için (1. satırda) Encapsulation projesini dahil ettik).

Employee.cs

Employee sınıfımız da Person sınıfından kalıtım alıyor. Name, Phone ve getPersonInfo varlıklarından bahsetmiştik.

protected internal : MotherName özelliği Encapsulation projesinin içerisinde public gibi davranmıştı (internal belirtecinin özelliğini hatırlayın). Employee sınfı farklı bir projede (Encapsulation_2) olmasına rağmen MotherName özelliği erişilebilir durumda (protected belirtecinin özelliğini hatırlayın).

Program.cs

private protected : Bu belirteci protected belirtecinin kısıtlanmış hâli gibi düşünebiliriz. Şöyle ki ; farklı projelerdeki sınıflar Person sınıfından kalıtım alsa dahi bu belirteç ile işaretlenmiş özelliklere erişemezler. FatherName özelliği Encapsulation altında olan Student sınıfından erişilebilirken, Encapsulation_2 altında olan Employee sınıfı tarafından erişilememekte.

Abstraction (Soyutlama)

Soyutlama; operasyonel detayları yazılımcıdan gizlemeye yarar. Sınıflarımızın ortak yönlerini tekilleştirir ve zaruri özellikler kazandırarak sınıflarımızı belli bir hizaya çeker. Rahatça generic yapılar kurmamızı sağlar. Bir örnek vermek gerekirse; kontağı çeviririz ve otomobil çalışır. Muhakkak kontağı çevirdiğimizde arka planda gerçekleşen çok fazla anlamlı iş vardır. Lakin yaşananları bilmeyiz ve belki bilmekte istemeyiz (ihtiyacımız veya zamanımız olmayabilir). Bizim için önemli olan otomobili güvenli ve tam randımanlı kullanmaktır. Soyutlama için interface ve abstract sınıfları kullanırız.

AbstractProduct.cs
AbstractProduct.cs

Abstract sınıfların varsayılan belirteci internal’dır. Public ve internal harici erişim belirteci kullanamazlar. abstract olarak işaretlenmiş metot bildirimleri public, protected, internal ve protected internal belirteçlerine sahip olabilirler (somut metotlar için erişim belirteci kısıtı yoktur).

IProduct.cs

Interface’lerin varsayılan belirteci publictir. Interface’ler public ve internal(C# 7.2‘den itibaren) harici belirteç kullanamaz. Interface metotlarının varsayılan belirteci public’tir ve farklı bir belirteç kullanamazlar.

Yukarıda AbstractProduct isimli bir abstract sınfı ve IProduct isimli bir interface var. Computer isimli sınıfımız bu varlıklardan kalıtım alacak. Bu varlıkların sınıfa implemente edilmesi için zorladıkları metotlara bir bakalım.

Computer.cs

Hata mesajında da görüldüğü üzere sınıfın Repair metodunun implemente edilmediğini söylüyor. (AbstractProduct sınıfından gelen Sell metodu abstract anahtar kelimesi ile işaretlenmemiş, bir gövdesi var. Yani somut bir metot. virtual olarak işaretlenmediği için de override edilemez. Velhasıl bu metot direkt olarak kalıtım yoluyla sınıfa aktarılacaktır.) Benzer şekilde ;

Computer.cs

IProduct interface’inin zorladığı GetPrice metodu da implemente edilmek zorunda. Computer sınıfımız finalde aşağıdaki gibi bir hâl aldı.

Computer.cs
Program.cs

9. ve 10. satırda abstract sınıfların ve interface’lerin nesnesinin yaratılamadığını görmüş olduk.

12. 13. ve 14. satırlarda Computer sınıfına ait nesneler yaratıyoruz. Bu nesnelerin referanslarına dikkat edersek sırasıyla IProduct, AbstractProduct ve Computer. 16. 17. ve 18. satırlarda Computer sınıfımızın metodlarını çağırmaya çalışıyoruz. GetPrice metodu hariç diğer 2 metot hata veriyor. Çünkü Computer nesnesini refere eden IProduct interface’inde bu tanımlar yok. Benzer şekilde 22. 23. ve 24. satırlarda abstract sınıfın refere ettiği nesne ile GetPrice metodunu çağıramıyoruz. Bu bilgiler ışığında IProduct ve AbstractProduct ‘a ait ekran görüntüsünü tekrar kontrol edin. Computer referansına sahip Computer nesnesi ile her 3 metodu da başarıyla çağırabiliyoruz. 12. ve 13. satırlarda uyguladığımız soyutlama kavramının bize kazandırdığı gücü hayal edin.

Polymorphism (Çok biçimlilik)

Çok biçimlilik; sınıfların kendi işlevselliklerine/amaçlarına göre, implemente ettikleri operasyonları farklılaştırmasıdır. Bu konuda bize interface’ler, abstract sınıflar ve kalıtım yardımcı olur.

IPolygon.cs

IPolygon interface’imiz CalculateArea metoduna sahip ve bu interface’i aşağıda sınıflara uygulayacağız.

AbstractPolygon.cs

AbstractPolygon abstract sınıfının 3 adet metodu olsun. Hello metodu override edilemez zira virtual olarak işaretlenmemiş (somut metot). CalculateVolume metodu (sanal metot) eğer istenirse override edilebilir. CalculatePerimeter metodu (soyut metot) kalıtım alan sınıflarda override edilmek zorunda olacak.

RightTriangle.cs

CalculateArea metodunu IPolygon interface’i , CalculatePerimeter metodunu AbstractPolygon sınıfı implemente etmeye zorladı. Ve metotların gövdelerini dik açının alan ve çevre formülleriyle doldurduk. Abstract sınıfında bulunan Hello ve CalculateVolume metotlarına RightTriangle içerisinde müdahale etmedik.

Square.cs

Benzer şekilde Square sınıfında da CalculateArea ve CalculatePerimeter metotlarını implemente ettik. (Dik üçgen ve karenin alan/çevre formülleri farklıdır. Formülleri uygun şekilde kodladık.) CalculateVolume metodu RightTriangle sınıfında override edilmemişti ve abstract sınıfında tanımlandığı gibi davranacak. Fakat Square sınıfında bu metodu override ederek gövdesini yeniden yazdık.

Program.cs

Program kodunu ve çıktısını yan yana görüyoruz. 14. ve 27. satırlarda Hello metodu benzer şekilde çalışıyor. 15. ve 28. satırlardaki CalculateVolume metodunun farklı çıktılar verdiğini görüyoruz. Square sınıfını hatırlayın, bu metodu override etmiştik, ve metodun çokbiçimli davrandığına şahit oluyoruz. CalculatePerimeter ve CalculateArea metotlarına dikkat edin. AbstractPolygon sınıf tanımında, IPolygon interface metot bildiriminde ve hatta RightTriangle ve Square nesnelerinden yaptığımız erişimlerde(16, 17 ve 29, 30 satırlar) herhangi bir farklılık görünmüyor. Fakat bu sınıfların içerisindeki metot gövdelerinde farklılıklar var. Dik üçgenin alanı hesaplanırken dik kenarlar çarpılıp ikiye bölünüyor, karenin alanı hesaplanırken taban çarpı yükseklik yapılıyor. Bu şekilde çok biçimliliği meydana getirmiş olduk.

Nesne yönelimli programlamanın detaylarını/ana fikirlerini anlatmaya çalıştık. Bunun yanında C# diline özgü bir kaç bilgi de paylaşmış olabiliriz. Farklı dillerle çalışan arkadaşlar bu bilgileri önemsemeyebilir. Yukarıdaki örneklere aşağıdaki repodan ulaşabilirsiniz. OOP üzerine kurulu olan SOLID Prensipleriyle ilgili yazıma buradan ulaşabilirsiniz. Gelişmeye ve geliştirmeye devam edelim. Bir sonraki yazımızda görüşmek dileğiyle…

--

--