BİL107U
Ünite 7: Nesneye Yönelik Kavramlar I
PROGRAMLAMA I
Giriş Nesne yönelimli programlama, daha modüler ve düzenli kod geliştirilmesine imkân vermektedir. Bu yaklaşım ile projelerin yönetilebilirliği artmış ve daha büyük projeleri yönetmek daha kolay hâle gelmiştir. Nesne yönelimli programlamanın gerçekleştirilebilmesi için belirli kurallara uyulması gerekmektedir. Miras Alma (Inheritance) Miras alma ya da kalıtım, nesne yönelimli programlamanın önemli kavramlarından biridir. Miras alma, programlama dillerinde de günlük hayattaki gibi atadan çocuğa tüm bırakılanları ifade etmektedir. Yani bir sınıftaki tüm sınıf üyelerini tekrar kullanan ve bunları genişleterek yeni sınıflar tanımlamamızı sağlayan bir tekniktir. C# programlama dilinde kullanımı şu şekildedir: class TemelSinif { } class TuretilmisSinif : TemelSinif { } C# dilinde, çoklu kalıtıma izin verilmez fakat türetilen bir sınıftan yeni bir sınıf türetilmesine izin verilmektedir. C# programlama dilinde kalıtımın yapılabilmesi için uyulması gereken bazı kurallar mevcut. Şimdi bu kurallara değinelim:
• Kalıtım yoluyla türetilen sınıf, varsayılan yapıcı (constructor), statik yapıcı (static constructor) ve yıkıcı (destructor) metotları devralamaz.
• Türetilen sınıf, temel sınıfın üyelerini erişim izinleri kuralları çerçevesinde devralmaktadır.
• Türetilen sınıflar, aynı anda sadece bir sınıftan türetilebilir, çoklu kalıtım desteklenmez.
• public erişim belirleyicisine sahip üyelere türetilmiş sınıflardan ve bu sınıfların public olan ara yüzlerinden erişilebilir.
• private erişim belirleyicisine sahip üyelere türetilen sınıftan erişilemez. Sadece sınıfın kendi içinden ve iç içe tanımlı sınıflardan erişilebilir.
• protected erişim belirleyicisine sahip üyelere sadece türetilmiş sınıflar içerisinden erişilebilir.
• internal erişim belirleyicisine sahip üyelere aynı derleyicide (assembly) türetilmiş sınıflar tarafından erişilebilir. Çoklu ve Hiyerarşik Miras Alma C# programlama dilinde, bir sınıf sadece bir sınıftan türeyebilir. Bunun yanında bir sınıf, birden fazla arayüz (interface) yapısından da kalıtım alabilir. Çoklu kalıtımla yapmak istediğimiz işlemleri, arayüzler (interface) ile destekleyerek yapabiliriz. Peki neden birden fazla arayüzden kalıtım alabiliyorken birden fazla sınıftan kalıtım ile sınıf türetemiyoruz? Cevap olarak, C++, Python,
Lisp, Perl gibi dillerin çoklu kalıtımı desteklerken karşılaştıkları elmas (Diamond) problemi karşımıza çıkıyor. Konu ile ilgili bir başka soru da nasıl oluyor da birden fazla arayüzden kalıtım alınabildiğidir? Cevabı çok basit: Arayüzler, içlerinde herhangi bir kod bloğu barındırmayan, sadece metotların şablon tanımlarını içeren yapılar olduğundan, aynı metotlar farklı arayüzlerde geçse bile, metot tanımı sadece bir kere yapılacağı için herhangi bir belirsizlik durumu ortaya çıkmayacaktır. Miras Alma İşlemlerinde Erişim Denetleyicileri Erişim denetleyicileri, bir sınıftaki metot ve özellikler gibi tüm üyelere olan erişimin sınırlarını belirleyen anahtar kelimelerdir. Temel sınıftan türetilmiş sınıf, temel sınıfın sahip olduğu değişken ve metotları sanki kendi içerisinde tanımlanmış gibi kullanabilir. Yalnız, temel sınıfın erişim denetleyicileri “public” ve “protected” olarak tanımlanan elemanları türetilmiş sınıfa kalıtım yoluyla aktarılırken “private” olan elemanlar kalıtım yoluyla aktarılmaz.
• Public: Kalıtım ile aktarılır, her yerden erişilebilir.
• Private: Kalıtım ile aktarılmaz, sadece tanımlandıkları sınıf içerisinden erişilebilir.
• Protected: Kalıtım ile aktarılır, tanımlandıkları ve kendisinden türetilen sınıflardan erişilebilir.
• Internal: Kalıtım ile aktarılır, sadece bulunduğu projede erişilebilir. Yapıcı Metotlar ile Miras Alma İşleminin İlişkisi Sınıflar kalıtım ile türetilirken temel sınıfın tüm değişken ve metotları da türetilen sınıfta erişim yetkileri çerçevesinde kullanılabilir. Burada aklımıza şöyle bir soru gelebilir: Türeyen sınıftan üretilen yeni bir nesne için kendi sınıfının yapıcı (constructor) metodu çağrılır. Peki, türetildiği sınıfın yapıcı metodu da çağrılır mı? Bu soruya verilecek cevap evettir, türetildiği sınıfın da yapıcı metodu çağrılır. Bunun yanında, temel sınıf olduğu için ilk önce sınıfın türetildiği temel sınıfın yapıcı metodu çağrılır. Sınıf Bağımlılıkları (Class Coupling) Sınıfların bağımlılığı konusuna “Sıkı Bağlı” (Tightly Coupled) kavramını açıklamak ile başlayabiliriz. Bir sistem, belli bir nesne olmadan çalışamıyorsa, ona mutlaka ihtiyaç duyuyorsa, o nesneye sıkı sıkıya bağlı olduğunu söyleyebiliriz. Sayfa 173’deki örnek kodu incelediğimizde, kodun çalıştırıldığı Main metodunu içeren Program sınıfı mevcut. Main metodu içinde dosya okuyucu sınıftan bir nesne oluşturuluyor. Oluşturulan nesnenin Oku ve VeriKaydet metotları çağrılarak işlem yapılıyor. VeriKaydet metodu, içinde DosyaKayit sınıfına ait nesne oluşturarak bunun üzerinden dosya kayıt işlemini yapıyor. DosyaOku ve DosyaKayit sınıfları, birbirleri ile iç içe girerek sıkı bağlandığı için bu sınıflarda ek değişiklikler yapmak zordur. Bunun nedeni de kod içinde bir güncelleme yapılmak istendiğinde, değişikliklerin birçok yerde
yapılmasına ihtiyaç duyulmasıdır. Daha büyük ve karmaşık projelerde bu durumlarla karşılaşmak, kodu içinden çıkılması ve anlaşılması güç bir hâle sokacaktır. Gevşek Bağlı (Loosely Coupled) kavramı da sıkı bağlılığın tam tersidir. Buna günlük hayattan şapkayı örnek olarak verebiliriz. Spor bir şapka veya bir kasketten herkes kendi kafasına ve zevkine uygun olanı alıp kullanır. Burada kafa ve şapkanın birbirine bağımlılığı olmadığı için gevşek bağlı olduğunu söyleyebiliriz. Aynı şekilde sınıf ve nesnelerin birbirine gevşek bağlı olduğu durumlarda, kodun güncellenmesi, tekrar kullanılması ve bakımı daha kolay ve hızlı olacaktır. Kodun istenilen şekilde değiştirilmesine izin veren, modüler bir yapıda hazırlanması bir gerekliliktir. Bu gereklilik ise gevşek bağlantılı kod oluşturmak ile mümkündür. Küçük uygulamalarda sıkı bağlılık her ne kadar sorun olmasa da uygulama üzerinde büyük değişiklikler çıktıkça uygulamanın tekrar yazılmasına ihtiyaç duyulacaktır. Kompozisyon (Composition) ve Birleştirme (Aggregation) Kompozisyon, bir sınıfın diğer sınıfı içermesine izin veren sınıflar arasındaki ilişki türüdür. Kompozisyonda içerilen ya da sahip olunan nesne, sahip olan nesneden bağımsız bir şekilde var olamaz. Örneğin, bir dizüstü bilgisayarın ekranının, dizüstü bilgisayar dışında bir kullanımı yoktur. Kompozisyon da tıpkı kalıtım gibi kodun yeniden kullanılmasına izin verir. Nesnelerin ömürleri açısından bakıldığında, sahip olan nesne ile sahip olunan nesnenin ömürleri yaklaşık aynıdır. Birleştirme (Aggregation) ise içerilen ya da sahip olunan nesnenin, sahip olan nesneden bağımsız olarak var olabilmesine denmektedir. Buna da yine dizüstü bilgisayar örneğinden devam ederek dizüstü bilgisayar ile çantasını örnek verebiliriz. Çanta ve dizüstü bilgisayarın ayrı ayrı işlevsellikleri vardır, birbirlerine bağlı değillerdir. Sonuç olarak aralarındaki temel fark sınıfların bağımsızlıklarıdır. Kompozisyonda, alt sınıfların yaşam döngüsü, doğrudan üst sınıfın yaşam döngüsüne bağlıdır ve bunun tersi de geçerlidir. Birleştirmede (Aggregation) ise alt sınıfların ve üst sınıfın kendi yaşam döngüleri vardır. Kapsülleme (EnCapsulation) Kapsülleme, nesne yönelimli programlamanın önemli kavramlarından biridir. Bir sınıfın üyelerinden, sınıf dışında görevi olmayan, sadece sınıf içi işlemlerde kullanılacak olanlar private bölüme yerleştirilir. Dış dünyaya sadece public arayüzü ile tanımlı olan üyelerin servis edilmesi şeklinde tanımlanabilir. Public tanımlı üyeler içinde de private tanımlı üyeler ile ilgili işlemler yapılabilir. Kapsülleme işlevi güzel gerçekleştirilmiş bir sınıf, kullananlarda da kafa karışıklığına neden olmaz. Bu duruma bilgisayar üzerinden örnek verebiliriz. Bilgisayarın
ekranı, klavyesi public tanımlı olarak dış dünyaya açılmışken, harddiski, ram, işlemci ve anakart gibi bileşenleri private tanımlı olup kullanıcılardan saklanmıştır. Generics Generics, genel, geniş kapsamı olan manasında kullanılmaktadır. C# programlama dilinde de yine aynı şekilde genel, belirli bir veri türüne özgü olmayan anlamına gelmektedir. C#, belirli bir veri türü olmadan, tür parametresini kullanarak genel sınıflar, arabirimler, soyut sınıflar, alanlar, metotlar, statik metotlar, özellikler, olaylar, temsilciler ve operatörler tanımlanmasına olanak tanır. Biraz daha açacak olursak “generics”, tasarlanan arayüz, sınıf, metot ya da parametrelerin double, int, string gibi belirli bir tür yerine bir şablon yapısına uyan her bir tür için çalışmasına olanak veren bir yapıdır. Kullanımının sağladı faydaları şu şekilde sıralayabiliriz:
• Tekrar kullanılabilir kod yazımına yardımcı olarak tekrarı önler.
• Kod üzerinde daha esnek ve güçlü bir kontrol sağlanmasına yardımcı olur.
• Hata ayıklaması kolay ve yönetilebilir kodların yazılmasına imkân verir.
• Derleme zamanında (compile time) tip güvenli değişken kullanılması zorunlu olduğu için, çalışma zamanında oluşabilecek tür dönüşüm hatalarını engeller.
• Çalışma zamanında (runtime) tür dönüşümü (casting, boxing-unboxing) işlemlerini önleyerek kodun daha verimli ve hızlı çalışmasını sağlar. Generic ifadeler genellikle “T” ile gösterilmektedir. Nesne ya da sınıf tanımı yapılırken T’ye atanan veri türü ile işlem yapılmaktadır. Burada dikkat edilmesi gereken nokta, derleyicimiz T gördüğü her yere atadığımız bu veri türünü koymaktadır. Bu yüzden T’ye atanan veri türü harici bir veri atanmaya çalışılır ise derleme zamanı hatası alınacaktır.
• Kalıtım yoluyla türetilen sınıf, varsayılan yapıcı (constructor), statik yapıcı (static constructor) ve yıkıcı (destructor) metotları devralamaz.
• Türetilen sınıf, temel sınıfın üyelerini erişim izinleri kuralları çerçevesinde devralmaktadır.
• Türetilen sınıflar, aynı anda sadece bir sınıftan türetilebilir, çoklu kalıtım desteklenmez.
• public erişim belirleyicisine sahip üyelere türetilmiş sınıflardan ve bu sınıfların public olan ara yüzlerinden erişilebilir.
• private erişim belirleyicisine sahip üyelere türetilen sınıftan erişilemez. Sadece sınıfın kendi içinden ve iç içe tanımlı sınıflardan erişilebilir.
• protected erişim belirleyicisine sahip üyelere sadece türetilmiş sınıflar içerisinden erişilebilir.
• internal erişim belirleyicisine sahip üyelere aynı derleyicide (assembly) türetilmiş sınıflar tarafından erişilebilir. Çoklu ve Hiyerarşik Miras Alma C# programlama dilinde, bir sınıf sadece bir sınıftan türeyebilir. Bunun yanında bir sınıf, birden fazla arayüz (interface) yapısından da kalıtım alabilir. Çoklu kalıtımla yapmak istediğimiz işlemleri, arayüzler (interface) ile destekleyerek yapabiliriz. Peki neden birden fazla arayüzden kalıtım alabiliyorken birden fazla sınıftan kalıtım ile sınıf türetemiyoruz? Cevap olarak, C++, Python,
Lisp, Perl gibi dillerin çoklu kalıtımı desteklerken karşılaştıkları elmas (Diamond) problemi karşımıza çıkıyor. Konu ile ilgili bir başka soru da nasıl oluyor da birden fazla arayüzden kalıtım alınabildiğidir? Cevabı çok basit: Arayüzler, içlerinde herhangi bir kod bloğu barındırmayan, sadece metotların şablon tanımlarını içeren yapılar olduğundan, aynı metotlar farklı arayüzlerde geçse bile, metot tanımı sadece bir kere yapılacağı için herhangi bir belirsizlik durumu ortaya çıkmayacaktır. Miras Alma İşlemlerinde Erişim Denetleyicileri Erişim denetleyicileri, bir sınıftaki metot ve özellikler gibi tüm üyelere olan erişimin sınırlarını belirleyen anahtar kelimelerdir. Temel sınıftan türetilmiş sınıf, temel sınıfın sahip olduğu değişken ve metotları sanki kendi içerisinde tanımlanmış gibi kullanabilir. Yalnız, temel sınıfın erişim denetleyicileri “public” ve “protected” olarak tanımlanan elemanları türetilmiş sınıfa kalıtım yoluyla aktarılırken “private” olan elemanlar kalıtım yoluyla aktarılmaz.
• Public: Kalıtım ile aktarılır, her yerden erişilebilir.
• Private: Kalıtım ile aktarılmaz, sadece tanımlandıkları sınıf içerisinden erişilebilir.
• Protected: Kalıtım ile aktarılır, tanımlandıkları ve kendisinden türetilen sınıflardan erişilebilir.
• Internal: Kalıtım ile aktarılır, sadece bulunduğu projede erişilebilir. Yapıcı Metotlar ile Miras Alma İşleminin İlişkisi Sınıflar kalıtım ile türetilirken temel sınıfın tüm değişken ve metotları da türetilen sınıfta erişim yetkileri çerçevesinde kullanılabilir. Burada aklımıza şöyle bir soru gelebilir: Türeyen sınıftan üretilen yeni bir nesne için kendi sınıfının yapıcı (constructor) metodu çağrılır. Peki, türetildiği sınıfın yapıcı metodu da çağrılır mı? Bu soruya verilecek cevap evettir, türetildiği sınıfın da yapıcı metodu çağrılır. Bunun yanında, temel sınıf olduğu için ilk önce sınıfın türetildiği temel sınıfın yapıcı metodu çağrılır. Sınıf Bağımlılıkları (Class Coupling) Sınıfların bağımlılığı konusuna “Sıkı Bağlı” (Tightly Coupled) kavramını açıklamak ile başlayabiliriz. Bir sistem, belli bir nesne olmadan çalışamıyorsa, ona mutlaka ihtiyaç duyuyorsa, o nesneye sıkı sıkıya bağlı olduğunu söyleyebiliriz. Sayfa 173’deki örnek kodu incelediğimizde, kodun çalıştırıldığı Main metodunu içeren Program sınıfı mevcut. Main metodu içinde dosya okuyucu sınıftan bir nesne oluşturuluyor. Oluşturulan nesnenin Oku ve VeriKaydet metotları çağrılarak işlem yapılıyor. VeriKaydet metodu, içinde DosyaKayit sınıfına ait nesne oluşturarak bunun üzerinden dosya kayıt işlemini yapıyor. DosyaOku ve DosyaKayit sınıfları, birbirleri ile iç içe girerek sıkı bağlandığı için bu sınıflarda ek değişiklikler yapmak zordur. Bunun nedeni de kod içinde bir güncelleme yapılmak istendiğinde, değişikliklerin birçok yerde
yapılmasına ihtiyaç duyulmasıdır. Daha büyük ve karmaşık projelerde bu durumlarla karşılaşmak, kodu içinden çıkılması ve anlaşılması güç bir hâle sokacaktır. Gevşek Bağlı (Loosely Coupled) kavramı da sıkı bağlılığın tam tersidir. Buna günlük hayattan şapkayı örnek olarak verebiliriz. Spor bir şapka veya bir kasketten herkes kendi kafasına ve zevkine uygun olanı alıp kullanır. Burada kafa ve şapkanın birbirine bağımlılığı olmadığı için gevşek bağlı olduğunu söyleyebiliriz. Aynı şekilde sınıf ve nesnelerin birbirine gevşek bağlı olduğu durumlarda, kodun güncellenmesi, tekrar kullanılması ve bakımı daha kolay ve hızlı olacaktır. Kodun istenilen şekilde değiştirilmesine izin veren, modüler bir yapıda hazırlanması bir gerekliliktir. Bu gereklilik ise gevşek bağlantılı kod oluşturmak ile mümkündür. Küçük uygulamalarda sıkı bağlılık her ne kadar sorun olmasa da uygulama üzerinde büyük değişiklikler çıktıkça uygulamanın tekrar yazılmasına ihtiyaç duyulacaktır. Kompozisyon (Composition) ve Birleştirme (Aggregation) Kompozisyon, bir sınıfın diğer sınıfı içermesine izin veren sınıflar arasındaki ilişki türüdür. Kompozisyonda içerilen ya da sahip olunan nesne, sahip olan nesneden bağımsız bir şekilde var olamaz. Örneğin, bir dizüstü bilgisayarın ekranının, dizüstü bilgisayar dışında bir kullanımı yoktur. Kompozisyon da tıpkı kalıtım gibi kodun yeniden kullanılmasına izin verir. Nesnelerin ömürleri açısından bakıldığında, sahip olan nesne ile sahip olunan nesnenin ömürleri yaklaşık aynıdır. Birleştirme (Aggregation) ise içerilen ya da sahip olunan nesnenin, sahip olan nesneden bağımsız olarak var olabilmesine denmektedir. Buna da yine dizüstü bilgisayar örneğinden devam ederek dizüstü bilgisayar ile çantasını örnek verebiliriz. Çanta ve dizüstü bilgisayarın ayrı ayrı işlevsellikleri vardır, birbirlerine bağlı değillerdir. Sonuç olarak aralarındaki temel fark sınıfların bağımsızlıklarıdır. Kompozisyonda, alt sınıfların yaşam döngüsü, doğrudan üst sınıfın yaşam döngüsüne bağlıdır ve bunun tersi de geçerlidir. Birleştirmede (Aggregation) ise alt sınıfların ve üst sınıfın kendi yaşam döngüleri vardır. Kapsülleme (EnCapsulation) Kapsülleme, nesne yönelimli programlamanın önemli kavramlarından biridir. Bir sınıfın üyelerinden, sınıf dışında görevi olmayan, sadece sınıf içi işlemlerde kullanılacak olanlar private bölüme yerleştirilir. Dış dünyaya sadece public arayüzü ile tanımlı olan üyelerin servis edilmesi şeklinde tanımlanabilir. Public tanımlı üyeler içinde de private tanımlı üyeler ile ilgili işlemler yapılabilir. Kapsülleme işlevi güzel gerçekleştirilmiş bir sınıf, kullananlarda da kafa karışıklığına neden olmaz. Bu duruma bilgisayar üzerinden örnek verebiliriz. Bilgisayarın
ekranı, klavyesi public tanımlı olarak dış dünyaya açılmışken, harddiski, ram, işlemci ve anakart gibi bileşenleri private tanımlı olup kullanıcılardan saklanmıştır. Generics Generics, genel, geniş kapsamı olan manasında kullanılmaktadır. C# programlama dilinde de yine aynı şekilde genel, belirli bir veri türüne özgü olmayan anlamına gelmektedir. C#, belirli bir veri türü olmadan, tür parametresini kullanarak genel sınıflar, arabirimler, soyut sınıflar, alanlar, metotlar, statik metotlar, özellikler, olaylar, temsilciler ve operatörler tanımlanmasına olanak tanır. Biraz daha açacak olursak “generics”, tasarlanan arayüz, sınıf, metot ya da parametrelerin double, int, string gibi belirli bir tür yerine bir şablon yapısına uyan her bir tür için çalışmasına olanak veren bir yapıdır. Kullanımının sağladı faydaları şu şekilde sıralayabiliriz:
• Tekrar kullanılabilir kod yazımına yardımcı olarak tekrarı önler.
• Kod üzerinde daha esnek ve güçlü bir kontrol sağlanmasına yardımcı olur.
• Hata ayıklaması kolay ve yönetilebilir kodların yazılmasına imkân verir.
• Derleme zamanında (compile time) tip güvenli değişken kullanılması zorunlu olduğu için, çalışma zamanında oluşabilecek tür dönüşüm hatalarını engeller.
• Çalışma zamanında (runtime) tür dönüşümü (casting, boxing-unboxing) işlemlerini önleyerek kodun daha verimli ve hızlı çalışmasını sağlar. Generic ifadeler genellikle “T” ile gösterilmektedir. Nesne ya da sınıf tanımı yapılırken T’ye atanan veri türü ile işlem yapılmaktadır. Burada dikkat edilmesi gereken nokta, derleyicimiz T gördüğü her yere atadığımız bu veri türünü koymaktadır. Bu yüzden T’ye atanan veri türü harici bir veri atanmaya çalışılır ise derleme zamanı hatası alınacaktır.