Nesne Yönelimli Programlama Yaklaşımı

Geçmişten günümüze programlama konusu pek çok farklı aşamadan geçmiş ve pek çok evrim geçirmiştir. Sadece 1 ve 0’lardan meydana gelen makine kodlarından tutun, tıpkı bir konuşma dili kullanır gibi bir programlama dili kullanarak program yazma ve grafiksel programlama dillerine kadar oldukça farklı programlama mekanizmaları ortaya çıkmıştır. Geçmişte makineye yakın olan programlama dilleri, artık neredeyse herkesin rahatça okuyabileceği ve yazabileceği seviyeye gelmiştir.

Programlama dillerinde bu tip yapısal değişikliklerin yanında, programlamayı nasıl yapacağımız ile ilgili de birtakım değişiklikler olmuştur. İnsanlar çok farklı programlama yöntemleri (yaklaşımları) geliştirmeye başlamışlardır. Örneğin C dili prosedürel bir programlama dilidir. Bunun anlamı bu dilde işler çeşitli adımların bir araya gelmesi ile oluşturulmuş olan prosedürlerin çağrılması ile yürümektedir. Fortran, ALGOL, COBOL ve Pascal gibi diller de buna uymuştur.

Prosedürel programlama yaklaşımının dışında pek çok farklı yaklaşım ve bununla birlikte de pek çok farklı programlama dili ortaya çıkmıştır. Bunun sonucu olarak programlama paradigması (programming paradigm) adı verilen bir kavram ortaya çıkmıştır. Programlama paradigması programlama dillerini özelliklerine göre sınıflandırmanın bir yoludur. Bir programlama dili birden fazla paradigmaya sahip olabilir.[1] Temel olarak paradgimalar iki grupta incelenirler:

  • Imperative Programming (Emirli Programlama): En eski proramlama yaklaşımlarından biridir. Bu yaklaşım ile yazılmış programlar açıkça ifade edilen komutlardan meydana gelirler. Bu programlama yaklaşımı programın bir işlemi nasıl (how) gerçekleştireceği ile ilgilenir.
  • Declarative Programming (Bildirimli Programlama): Programlamada programın veya işin ne (what) yapması gerektiği üzerinde duran programlama yaklaşımıdır. Bu dillerde programcı sadece ne yapılması gerektiğini bildirir ve sonuç olarak istediğini elde eder.

Bu iki konunun açıklaması başka bir yazıya sığacak kadar geniş olduğu için ayrıntıya girmeyeceğim. Burada bizi ilgilendiren kısım Imperative Programing’dir. Imperative Programming de iki alt kısma ayrılır:

  • Prosedürel Programlama (Procedural Programming): Programın çeşitli prosedürlerin çağrılması yoluyla implement edilmesine dayanan programlama yaklaşımıdır.
  • Nesne Yönelimli Programlama (Object Oriented Programming): Programlama bileşenlerini gerçek hayattaki nesnelere benzeterek yazmaya çalışan bir yaklaşımdır.

Peki “nesne (object)” dediğimiz şey nedir? Aslında fiziksel dünyada gördüğümüz her şeye nesne (obje) diyebiliriz. Masa, kalem, bilgisayar, kıyafet birer nesnedir. Elbette fiziksel dünyada nesneler şekil, boyut, renk gibi özelliklerle birbirlerinden ayrılırlar. Bunun yanında bazı nesneler belli eylemleri de yerine getirebilirler. Örneğin bir araba nesnesi sizi bir yerden başka bir yere ulaştırmaya yarayan çeşitli eylemlere (gaza basmak, frene basmak, sinyal vermek gibi) sahip olabilir.

Peki programlama bu işin neresindedir? Algoritmalar hatırlayacağınız gibi belli eylemler ve onların yapılış sırasından meydana gelmekteydi. Ancak bu gerçek dünyadaki problemleri her zaman modelleyip programlamaya aktarmak için yeterli olmayabilir. İşte nesne yönelimli programlama dediğimiz şeye tam da burada ihtiyacımız olur. Bu yaklaşımda, tıpkı gerçek dünyadaki nesneler gibi belli özelliklere (properties) ve belli eylemlere (methods) sahip nesneler oluşturup onları kullanırız.

Bu konuyla ilgili bir analoji vermemiz sanırım konuyu daha iyi pekiştirecektir. Şimdi bir ev düşünelim. Bu ev fiziksel dünyada bir nesnedir. Evler (basit olarak düşünürsek) duvarlardan, camlardan ve kapılardan meydana gelmektedir. Ayrıca her evin değişik sayıda cam ve kapısı olabilir. Yani kapı ve pencere sayısı bir evin özelliğini oluşturabilir. Ev tek başına sadece kafada bir konsept oluşturabilir. Fakat Ahmet’in Evi veya Ayşe’nin Evi dediğimizde aklımızda onların kaç tane pencereye ve kapıya sahip oldukları hemen belirir. İşte burada bir X kişinin evi dediğimiz ev bir nesne belirtir.

O zaman bu örnekten yola çıkarak bir nesnenin hem konsept hali, hem de inşa edilmiş hali bulunmaktadır. İşte programlama dünyasında bir nesnenin hangi özelliklere ve eylemlere sahip “olabileceğini” belirten konsepte sınıf (class) adı verilir. Fakat bir nesnenin hangi özelliklere ve eylemlere sahip “olduğunu” belirten varlıklara ise nesne (object) adı verilir. Örneğin her evin bir penceresi vardır. Fakat Ahmet’in Evi’nin 4 penceresi varken, Ayşe’nin Evi’nin 2 penceresi olabilir. Yani burada aslında bu evleri birbirlerinden ayıran şey, özelliklerinin farklı oluşudur.

Sınıf ve nesne kavramlarını şöyle de düşünebilirsiniz: Evler inşa edilmeden önce taslak olarak mimarlar tarafından çizilirler. Ancak bu çizim sadece evin nasıl bir şekle sahip olabileceği ile ilgili bize bir fikir verir. Yani bu çizimi bir sınıf olarak görebiliriz. Ancak ev inşa edildikten sonra evin duvar renginden tutun pek çok özelliği belli bir değerde olabilir. Örneğin Ahmet ve Ayşe’nin evleri aynı taslağa sahip olsa bile Ahmet’in Evi yeşil, Ayşe’nin evi sarı olabilir. Bu da nesneleri birbirlerinden ayırır. Aşağıda bu ayrım ile ilgili bir görsel verilmiştir:

Figür 1: Sınıf ve Nesnenin Farkını Anlatan Analoji[2]

Programlama dünyasında nesnelere bazen örnek (instance) adı da verilmektedir. Fakat bu kavram genellikle sınıf ile nesne arasındaki bağlantıyı belirtmek için kullanılır. Yani “sınıflar nesneler için bir taslak görevi görürken, nesneler sınıfların birer örneğidir” gibi bir açıklama yaparken bu kelimeyi genellikle kullanırız. Bunun yanında bir nesnenin özelliği (property) yerine durumu (state) ve özniteliği (attribute) gibi kavramlar da kullanılabilmektedir. Değişik kaynaklarda farklı kullanımlar ile karşılaşmanız mümkündür.

Nesneler ve sınıflar arasındaki farklılığı açıkladıktan sonra, nesne yönelimli programlamanın temel yaklaşımlarını inceleyelim. Nesne yönelimli programlamada sınıflar ve onları baz alarak oluşturulan nesneler yer alır. Sınıflar aynı zamanda bir nesnenin türünü belirler. Örneğin int a değişken bildiriminde a değişkeninin türü int olduğu gibi, Ev b gibi bir bildirimde b nesnesinin türü Ev olur.

Programlamada sınıflar gibi çeşitli mekanikleri kullanarak oluşturulan türlere kullanıcı tanımlı tür (user-defined type) adı verilir. Programlamanın kendi içerisinde bulunan türlere ise gömülü tür (built-in type) adı verilir. Örneğin C++’ta gömülü (primary) türler ile kullanıcı tanımlı (user defined) türleri genel olarak şöyle gösterebiliriz:

Figür 2: C++’ta Veri Türü Kategorileri[3]

Şimdi de nesnelerin eylemlerinden biraz bahsedelim. Yine ev örneğimize geri dönersek, bir ev üzerinde uygulayabileceğimiz çeşitli eylemler vardır: Kapıyı açmak/kapatmak, pencereyi açmak/kapatmak ve kapıyı kilitlemek gibi… Aynı şekilde programlamadaki nesnelerin de belli davranışları (behaviour) olabilir. Biz prosedürel yaklaşımda, belli eylemi yerine getiren kod parçalarına fonksiyon (function) veya prosedür (procedure) gibi isimler vermekteydik. Nesne yönelimli programlamada ise bunları metot (method) olarak adlandırmaktayız. Yine farklı kaynaklarda farklı isimlendirmeler olabilir.

Nesne yönelimli programlamanın 4 ana prensibi bulunmaktadır. Bu prensipler nesne yönelimli programlama mantığını uygulamalara daha iyi yansıtabilmek amacıyla vardır. Bunları özetleyecek olursak:

  1. Soyutlama (Abstraction)
  2. Kapsülleme (Encapsulation)
  3. Kalıtım (Inheritance)
  4. Çok Biçimlilik (Polymorphism)

Soyutlama (abstraction) bir nesnenin detaylarından çok genel konsepti ile ilgilenilmesi, anlamına gelir. Örneğin bir arabanın içine bindiğinizde siz sadece onu sürebileceğiniz bir direksiyon ve gaz, fren, debriyaj pedalları ile karşılaşırsınız. Arabayı sürebilmek için gaza bastığınızda arabanın içinde neler döndüğünü veya direksiyonu çevirdiğinizde arabanın içinde aslında tekerlekleri nasıl döndürdüğünü bilmenize gerek yoktur. İşte nesne yönelimli programlamada da bir nesnenin tasarım aşamasında neleri yapabileceğini belirlemeye soyutlama diyoruz. Yani kısaca bir nesnenin bir diğer nesneyi kullanabilmesi için, onun detaylarına ihtiyacı olmaması durumudur.

Kapsülleme (encapsulation) ise bir nesnenin implementasyon detaylarını, o nesneyi kullanan kişiden gizlemek ve ona sadece ihtiyacı olan şeyleri sunmaktır. Yine araba örneğinde, motor arabanın önemli bir parçası olmasına rağmen, arabayı kullanan kişinin motor ile doğrudan temas etmesine ve hatta onu görmesine bile gerek yoktur. Ancak arabanın içinde bir motor bulunmaktadır. İşte kapsülleme ile programlamada bir sınıfın belirli özelliklerini ve eylemlerini o sınıfı kullanan kişiye açmak amaçlanmıştır. Yani kullanıcı sınıfın iç mekanizmasını etkileyecek özelliklere ve eylemlere erişemez. Böylelikle hata yapma riski azalır.

Soyutlama ve kapsülleme kavramları bazen birbirleri ile karıştırılmaktadır. Ancak bu iki kavram farklı şeyleri ifade eder. Soyutlama bir tasarım meselesidir. Yani bir nesnenin tasarımında, nesnenin bir işi nasıl yapacağından çok ne yapacağı ile ilgilenir. Kapsülleme ise nesnenin iç çalışmasına etki eden verileri dış kullanıcılardan saklar. Bunu yapabilmek için de sınıfta bir işin nasıl yapılacağının bilinmesi gerekir. Çünkü nelerin dışa açılacağına, nelerin ise iç mekanizmayı etkilediği için dışa açılmayacağına karar verilmesi gerekir. Soyutlama ve kapsülleme arasındaki genel farkları şöyle bir tabloda inceleyelim:

Soyutlama (Abstraction)Kapsülleme (Encapsulation)
Nesnenin tasarımı aşamasında karar verilecek bir meseledir.Nesnenin implementasyonu aşamasında karar verilecek bir meseledir.
Bir nesnenin işleri nasıl yapacağından çok, hangi işleri yapacağına karar vermek için kullanılır.Kapsülleme bir nesnenin iç detaylarını saklamak anlamına geldiğinden, bir nesnenin işleri nasıl yaptığını da bilmek gerekir.
İmplementasyon detaylarını kullanıcıdan saklar ve kullanıcıya sadece kullanabileceği şeyleri gösterir.Kodları ve verileri tek bir birimde toplayarak (kapsülleyerek) onu dış kullanıcılardan saklar.
Programlamada Abstract Class (Soyut Sınıf) ve Interface (Arayüz) gibi yapılarla gerçekleştirilir.Programlamada Access Modifier (Erişim Düzenleyiciler) adı verilen yapılar ile gerçekleştirilir.

Nesne yönelimli programlamada bir başka önemli bir konu ise, kalıtım (inheritance) olarak adlandırılan ve nesneler arasındaki aitlik ve sahiplik ilişkilerini açıklayan kavramdır. Yani bu kavram nesneler arasındaki hiyerarşiyi belirler. Bu hiyerarşi de bir nesnenin bir diğer nesneden hangi özellikleri ve eylemleri kalıtmasını (devralmasını) belirleyen şemadır. Peki bir nesnenin bir diğer nesneden özellik alması neden bu kadar önemlidir? Bunu hemen bir örnekle açıklamaya çalışalım.

Örneğin bir Kişi nesnesini baz alalım. Bu nesnenin isim, soyisim, adres gibi özellikleri ve konuşmak, yürümek gibi eylemleri olsun. Şimdi bir de Çalışan nesnesini baz alalım. Bu nesnenin az önceki Kişi nesnesine ek olarak aylık maaş ve iş pozisyonu gibi özellikleri de olsun. Şimdi bu iki nesneyi ayrı ayrı implement edersek pek çok farklı ortak özelliği ve eylemi ayrı ayrı yazmış oluruz. Fakat bir Kişi nesnesinin ismi ile Çalışan nesnesinin ismi birebir aynı şeyi ifade eder. İşte burada kalıtım önemli bir rol oynar. Eğer Çalışan nesnesini Kişi nesnesinden kalıtırsanız (türetirseniz), onun birçok özelliğini devralır ve üzerine de maaş ve poziyon gibi özellikleri ekleyebilirsiniz. Aynı şekilde Çalışan nesnesinden olan kalıtımda onun davranışlarını da devralabilir ve hatta bu davranışları (birazdan açıklayacağım çok biçimlilik mantığı ile) nesne türüne göre değiştirebilirsiniz.

Kalıtımda özellikleri alınacak olan nesneye ebeveyn (parent) adı verilirken, özellikleri alan nesneye ise çocuk (child) adı verilmektedir. Ancak programlama dünyasında bu kavramlar farklılaşır. Ebevyn olan sınıfa üst sınıf (superclass) veya taban sınıf (base class), çocuk olan sınıfa ise alt sınıf (subclass) veya türemiş sınıf (derived class) adı verilmektedir. Bu kavramlar da yine çeşitli kaynaklara göre değişebilir. Programlama dünyasında çok çeşitli kalıtım türleri vardır. bu türlerin sayısı yine farklı kaynaklarda farklı olarak ele alınmıştır. Ben temel olarak 5 türden kısaca bahsedip geçeceğim:

  • Tekli Kalıtım (Single Inheritance): Bir üst sınıf ve ona ait bir alt sınıftan meydana gelen en basit kalıtım türüdür.
  • Çok Seviyeli Kalıtım (Multi-Level Inheritance): Üst sınıf ve alt sınıfların zincirleme bir şekilde birbirlerini takip ettiği bir kalıtım türüdür.
  • Çoklu Kalıtım (Multiple Inheritance): Bir alt sınıfın birden fazla üst sınıfa sahip olabildiği bir kalıtım türüdür.
  • Hiyerarşik Kalıtım (Hierarchical Inheritance): Her alt sınıfın sadece bir üst sınıfının bulunduğu ve bir üst sınıfın ise birden fazla alt sınıfa sahip olabildiği kalıtım türüdür. Bu kalıtımlar bir ağaç yapısı oluşturur. En sık kullanılan kalıtım türüdür.
  • Hibrit Kalıtım (Hybrid Inheritance): Hiyerarşik ve çoklu kalıtım türlerinin bir birleşimidir.
Figür 3: Kalıtım Çeşitleri

Son olarak da çok biçimliklik (polymorphism) kavramına bakalım. Aslında bu biraz da kalıtıma bağlı bir kavramdır. Daha önce söylediğimiz gibi kalıtımda alt ve üst sınıfın paylaştığı özellikler ve metotlar vardı. Çok biçimlilik mantığında bu paylaşılan metotların ismi aynı olsa bile implementasyonu farklı olabilmektedir. Örneğin Şekil ismli bir sınıfımız olsun. Bu sınıftan türeyen Dikdörtgen, Daire ve Beşgen sınıflarımız olsun. “Şekil” sınıfının alan hesaplama ile ilgili bir fonksiyonu olsun. Bu fonksiyon alt sınıflar tarafından devralınacaktır. Yalnız her şeklin alanı farklı şekillerde hesaplanır. Bu durumu nasıl çözeriz?

İlk bakışta bu fonksiyonu kullanmadan her alt sınıfın kendine ait ayrı çizim fonksiyonu tanımlamasını söyleyebilirsiniz. Ancak bu oldukça kötü bir tasarım örneğidir. İşte çok biçimlilik burada devreye girer. Çizme fonksiyonunun dışarıdan kullanılan yapısı aynı olmasına rağmen, iç implementasyonu her sınıfa göre ayrı yapılabilir. Bu genellikle C++’ta sanal fonksiyon (virtual function) denilen bir dil özelliği ile yapılır. Bu konuyu da ileride göreceğiz. Kısaca çok biçimlilik, türetme yapılan bir sistemde alt sınıfın üst sınıfın kullanıldığı her yerde rahatlıkla kullanılmasını sağlar. Böylelikle üst sınıf türünden tanımlanmış çeşitli yerlerde istediğimiz alt sınıfı kullanabiliriz.

Paradigmaları ve nesne yönelimli programlama yaklaşımı ile ilgili temel prensipleri anlattık. Bu konuların her biri çok derin konulardır ve her biri ile ilgili ayrı ayrı yazılar yazılabilir. Burada sadece genel yapısıyla ele alıyoruz. Bundan sonraki yazılarda sınıfların, nesnelerin ve onlarla ilgili özelliklerin C++’ta nasıl implement edildiklerini sırayla inceleyeceğiz. Bu arada yeri gelmişken söyleyelim, C++ bir nesne yönelimli programlama dili değil, çok paradigmalı (multi-paradigm) bir programlama dilidir. Yani onunla hem nesne yönelimli, hem prosedürel hem de fonksiyonel programlama yapılabilir. C ise prosedürel bir programalama dilidir.

Bu yazıda kısaca paradigma kavramını ve onun türlerini açıklayıp nesne yönelimli programlamaya giriş yaptık. Nesne yönelimli programlama yaklaşımını çeşitli analojiler ile açıklamaya çalışıp gerçek hayat ile programlama dünyasını ilişkilendirdik. Ayrıca nesne yönelimli programlamanın 4 ana prensibine değindik ve bunlarla ilgili çeşitli örnekler verdik. Sonraki yazılarda artık C++ dili ile nesne yönelimli programlamaya giriş yapacağız.

REFERANSLAR

  1. https://en.wikipedia.org/wiki/Programming_paradigm
  2. https://www.tutorialrepublic.com/lib/images/class-object-relationship-illustration.png
  3. https://www.geeksforgeeks.org/user-defined-data-types-in-c/
5 1 vote
Article Rating
Subscribe
Bildir
guest

2 Yorum
Eskiler
En Yeniler Beğenilenler
Neslihan Çelik

Kalıtım kısmında çalışanla ilgili yerde (türetirseniz)
“tanımlaya ihtiyaç duymazsınız” diye hatalı bir kısım var onu düzeltmeniz gerekmekte onun dışında güzel bir yazı olmuş elinize sağlık .