Makale Özeti

Nesneye yönelik programlama üzerine yeni bir dizi yazıya başlıyoruz

Makale

Nesneler, Arayüzler, ve Memur Muzafferin Dönüşü...

 

Merhaba,

Bundan önceki yazılarımda genel olarak C#, ve .NET framework’in teknik altyapısı gibi konulara değinmiştim. Aslında bu yazı ile başlayan bir dizi yazı da bu konulara eğilecek, ancak artık geçen süre ile birlikte yazılımcıların .NET  ve C# ile samimiyetinin arttığını tahmin ederek biraz daha ileri ve ilgi çekici konulara eğilmeyi planlıyorum.

Bu yazıyla birlikte bir dizi yeni konuya birlikte bakmaya başlayacağız. Üzerinde durmak istediğim konular, nesneye yönelik programlamanın temel kavramlarında olan kalıtım, interface’ler ve sınıflarımızın tasarımı olacak. Doğal olarak birkaç cilt alacak konuları dört beş makale ile anlatmak mümkün değil, ancak .NET ile birlikte, nesneye yönelik programlamanın da anlaşılması daha da önemli bir gereksinim haline geldi, bu nedenle temel kavramları elimden geldiğince aktarmaya çalışacağım.

İsterseniz daha fazla sabrınızı zorlamayalım ve konularımıza muhtemelen bir çok yazılımcının kafasını kurcalayan bir soruya yanıt arayarak başlayalım.

 

Nesneye yönelmeli mi yönelmemeli mi ?

Şimdi bu nasıl soru diye soran arkadaşları duyar gibi oluyorum, ancak bence en başta yanıtlanması gereken soru bu. Benim yazdığım yazılarda sürekli anlatmaya çalıştığım, yazılım dünyasında artık önemi tartışılmaz hale gelmiş bir kavram olsa da  bu yaklaşım, kendisi ile yeni tanışan bir çok yazılımcıya “ne gerek var” dedirtebilecek bir takım uygulamaları beraberinde getiriyor. Kısa yoldan halledilebilecek gibi görünen sorunları nesneye yönelik programlamaya hafiften zorlayıcı bir tarzda destek veren C# gibi bir dille halletmeye kalktığınızda ve bir şeyler istediğiniz gibi gitmediğinde ilk tepkiniz muhtemelen neden bu dil bunu yapmama izin vermiyor olmuştur.Bu gibi tepkiler göstermese bile pek çok programcı bu yaklaşımın sağladığı olanakları tam olarak kullanmak yerine, eski alışkanlıklarını yeni bir dille yeni bir şekle sokarak sadece bir parça syntax değişikliğine gidiyor, ve bu durumda da bu paradigmanın gerçek gücünden faydalanamıyor.

 

Nesneye yönelik programlamaya destek vermeyen, veya az destekleyen bir dille çalışmaya alışmış bir yazılımcı iseniz, doğal olarak sınıflar, erişim hakları, kalıtım gibi kavramlar basit bir programı yazarken sanki size köstek olmak için ortaya çıkarılmış şeyler gibi görünebilir. Ancak seneler süren bir evrim sonucu ortaya çıkmış kavramların herhalde hiçbir işe yaramadığı halde şişirildiğini düşünmüyorsunuz değil mi ?

Ya da neden son yıllarda doğumunu izlediğimiz diller bu yaklaşıma, artık destek vermenin ötesine geçip mümkün olduğunca sizi bu yaklaşım çerçevesinde kod yazmaya zorluyor ? Örnek olarak neden C# global değişkenlere izin vermiyor ?  siz ne güzel üç tane int beş tane string, iki tane de for döngüsü ile işi bitirecek iken neden birileri boğazınıza constructor’ları, statik değişkenleri dayıyor :)

Yanıt kolayca verilecek gibi değil, işin aslı prosedürel programlamada verimli olarak kod yazan deneyimli bir programcıya nesneye yönelik programlamayı kabul ettirmeyi çok kolay bir iş olarak görmüyorum. Bu programcılar, veya farklı bir paradigmaya alışan, o yaklaşım ile verimli kod yazan kişiler, en azından başlangıçta nesneye yönelik programlamayı, bir  adet deve, ve gayet geniş bir hendek olarak görüyor :)

 Nesneye yönelik programlamanın amacı sadece sınıflar, kalıtım, çok biçimlilik gibi kavramlar ile az sayıda insanın anlayabileceği bir yaklaşım yaratmak değil. Bu teknikle kod yazmak için, yarı çıplak şekilde bir şelalenin altında meditasyon yaparak hazırlanmanız falan da gerekmiyor.

Amaç daha az hata ile, daha kolay yönetilebilen ve daha kolay kullanılabilen kod yaratmak. Yazılım geliştirme artık sadece bir iki arkadaşın kendi evinde rahat ortamında kahvesiyle çayıyla yaptığı bir eğlence değil ( tabii bu şekilde çalışmanın tadında hala hiçbir azalma da olmadı )

Yazılım artık dünyayı döndüren sektör olmaya aday. Bitler ve byte lar artık sadece masaların üzerindeki bej rengi kutularda yaşamıyor, cep telefonlarından uçaklara, arabalardan buzdolaplarına, televizyonlardan asansörlere kadar her yerde yazılıma ihtiyaç var.

Bu kadar hızla gelişen bir sektörde, rekabet ve yazılımcıların üzerindeki baskı günden güne artıyor. Artık kalabalık grupların son derece sistemli bir şekilde, deadline’ları yakalamaya çalıştığı, milyar dolarlık firmaların doğup battığı, ve dünyada herkesin öne geçmeye çalıştığı bir ortamda mücadele ediyoruz.

Böyle bir ortamda ayakta kalabilmek ve öne geçebilmek için bu ortamda verimliği kanıtlanmış yazılım geliştirme tekniklerine hakim olabilmek gerçekten önem yaşıyor. Nesneye yönelik programlama son derece geniş çaplı yazılımların yazılması, ve yönetilmesinde başarısını ıspatlamış ve verimliliğini kanıtlamış bir yaklaşım. Ancak genelde bir parça soyut kalan teori kısmı yüzünden pek çok kullanıcı bu programlama tekniğin somut olarak kullanmakta zorluk çekiyor. Bu arada bu noktada belirtmeden geçemeyeceğim, bizim bu yazılardaki konumuz olsa bile, hiçbir zaman tek bir yaklaşımın tüm problemleri çözmesi, her derde deva olması beklenemez, yani low level kod yazan, işletim sistemleri için aygıt sürücüsü tasarlayan insanlar için nesneye yönelik programlama pek de uygun olmayabilir. Bu yüzden lütfen burada yazdıklarımdan her işi bu teknikle yapabilirsiniz gibi bir sonuç çıkarmayın.

Bizim nesneye yönelik programlamaya yaklaşımımız da, mümkün olduğunca somut ve bize bir şeyler kazandıracak bir çerçeve üzerinde gerçekleşecek( ya da en azından benim amacım bu )

 

Sınıflar, ve sınıfların ilişkileri.

Bu konu ile ilgili bir giriş yazısı olarak kabul edebileceğimiz bu yazıda sınıfların ne işe yaradığını sizlere aktarmaya çalışmıştım.

Sınıflar arasındaki ilişki bu basit giriş yazısından çok daha kapsamlı ve karmaşık aslında. Bu cümle sakın sizi korkutmasın, kapsamlı olması anlaşılamaz olması anlamına gelmiyor. İsterseniz nesneye yönelik programlanın temel kavramlarına önceki örneklerimizle paralel örnekler vererek giriş yapalım:

 

Kalıtım

Kalıtım, ( inheritance ) nesneye yönelik programlamada kodun yeniden kullanımını (code reuse ) son derece kolay hale getiren bir özelliktir. Temel olarak bize sağladığı şey bir sınıfa ait özellik ve metodların ondan türeyen başka bir sınıf tarafından da kullanılabilmesidir.

Peki bu ne anlama geliyor şimdi ? Düşünün ki elimizde bir sınıf olarak tanımladığımız herhangi bir “şey” var. Yani memeliler, kuşlar, veya memurlar gibi sınıflandırma yolu ile belirli özelliklerini listeleyebildiğimiz bazı nesnelere sahibiz. Bu sınıflar arasında bazı ilişkiler tanımlayabiliriz. Mesela memeliler sınıfını ve atlar sınıfın ayrı ayrı yaratmak mümkündür, ancak gerçek hayattaki bir gerçeği bu noktada hatırlayalım: atlar birer memelidir! Yani memelilere ait genellemeler yaptığımızda bu genellemelerin bazıları atlar için de geçerli olabilir. Memeliler doğar, memeliler beslenir, memeliler ürer gibi ifadelerde memeliler yerine atlar kelimesini koysak yine doğru ifadeler elde ederiz.

İşte gerçek hayatta sınıflandırdığımız nesneler arasındaki bu ilişkiyi, nesneye yönelik programlamada da kullanabiliyoruz. Yani bir sınıfı tanımladığımız zaman başka sınıfların da bu sınıftan türemesini sağlayabiliyoruz.

Bu durumda bir sınıftan türeyen sınıflar, kalıtım ile türedikleri sınıfa ait özellikleri miras alabiliyorlar.( aslında inheritance kelime anlamı olarak miras almaya denk geliyor, kalıtım da uygun  bir tanım olduğu için biz onu kullanıyoruz. )

Peki ama bu miras alma / kalıtım ne kazandırıyor bize ? Çok basit, bizim için bir sınıfa ait bir davranışın tanımlanması ne demek ? O sınıfın içinde bir fonksiyon yazmamız demek. Yazılan bir fonksiyonu başka bir sınıf miras aldığında, o zaman o sınıf için o fonksiyonu bir kere daha yazmadan tekrar kullanabiliriz.

Şimdi bu kavramı, biraz daha gerçekçi bir örnek ile açmaya çalışalım. Önceki yazımızdaki memur sınıfını biraz daha düzenlenmiş şekilde ele alalım :

 

      public class Memur

      {

            private int yas;

            private string isim;

           

            public Memur()

            {

                  yas=30;

                  isim="Memur bey";

            }

            public int getyas()

            {

                  return yas;

            }

            public void setyas(int yeniyas)

            {

                  yas=yeniyas;

        }

            public string getisim()

            {

                  return isim;

            }

            public void setisim(string yeniisim)

            {

                  isim=yeniisim;

            }

            public void raporyaz()

            {

                  Console.WriteLine("Memurun raporu: Her şey yolunda");

            }

            public void  telefonet(string telno)

            {

            Console.WriteLine(telno + " numaralı telefonu aradım");

            }

      }

 

 

Burada gördüğümüz memur sınıfı yaşı, ismi olan, rapor yazıp telefon edebilen bir memuru tanımlıyor. Bu sayede gerçek hayattaki bir memuru yazılım içinde ifade edebiliyoruz. ( hala bu konudaki önceki yazıya bakmadıysanız, artık bundan sonrası size karmaşık gelmeye başlayabilir)

Bu temel özellikleri tanımladıktan sonra elde ettiğimiz bir memur sınıfından bir memur nesneyi oluşturup kullanırsak:

 

                  Memur muzaffer=new Memur();

                  Console.WriteLine("Benim adım " +muzaffer.getisim());

                  muzaffer.raporyaz();

                  muzaffer.telefonet("555 0845");

 

Bu Örneği Ornek1.cs dosyasında bulabilirsiniz.

 

Memur muzaffer bey bizim için telefon da, eder, rapor da yazar, buraya kadar bir sorunumuz yok. Ancak sadece muzaffer bey isimli bir memuru yaratmak için bir sınıf tasarlamadık tabii ki. Üstelik memur kavramı son derece genel, diyelim ki biz bu kavramı biraz daha özelleştirmek istiyoruz, ve bu sayede değişik devlet dairelerinde görev yapan memurları birer sınıf olarak elde edip kullanacağız.Ancak bu yeni memurları oluştururken, önceden yazdığımız bir takım özelliklerin her memura gerekeceği de aşikar. Yani hangi memurun yaşı ya da ismi olmaz ki ? Madem ki nesneye yönelik programlama tanımladığımız sınıflar arasında kalıtım ile özelliklerin aktarılmasına izin veriyor, biz de hemen bunu kullanarak daha gelişmiş bir Muzaffer modeli üretelim ...

 

public class TapuMemuru:Memur

      {

            public TapuMemuru()

            {

            }

      }

Burada gördünüz basit tanım ile, nesneye yönelik programlamada tasarımın en kafa patlatıcı kavramlardan birisi olan kalıtımın canlı bir örneğini görüyoruz.

TapuMemuru:Memur kısmı bu yeni sınıfın Memur sınıfından türediğini gösteren kısım, bu yazım biçimi bizim kullandığımız dil olan C# a özgü,farklı dillerde değişik sözdizimleri ile bu iki sınıf arasındaki ilişkiyi göstermek mümkün.

Tapumemuru sınıfı memur sınıfından türüyor. Yani onun sahip olduğu tüm özellikleri kalıtım ile alıyor. Bunun anlamı ne peki ?

 

 

TapuMemuru muzaffer=new TapuMemuru();

Console.WriteLine("Benim adım " +muzaffer.getisim());

                  muzaffer.raporyaz();

                  muzaffer.telefonet("555 0945");

(bu örneği muzaffer_tapu.cs dosyasında bulabilirsiniz. )

 

İşte bu satırlar ile tapumemuru, türediği sınıf olan memur sınıfından miras aldığı olanakları kullanıyor.

Hemen yukarıdaki tapu memurunu oluşturan sınıfa bakarsanız, burada memur sınıfındaki metod veya değişkenlerin bulunmadığını göreceksiniz.

Yani tapumemuru sınıfını tanımlarken, memur sınıfına ait her nesnenin yapacağı işleri yeniden yazmak zorunda kalmadık, bunlar otomatik olarak kalıtım ile taşındı !!! Yani basit bir mantıkla kelimelere dökebileceğimiz bir şeyi kod ile nasıl gerçekleştirdiğimizi gördük:

 

Memur sınıfının tüm üyelerinin bir yaşı ve ismi vardır, rapor yazabilirler, ve telefon edebilirler. Tapu memurları da memurdan türediği için birer memurdur ve onların da ismi vardır, rapor yazabilirler, ve telefon edebilirler.

 

 

Ancak tapu memurları memur sınıfından daha özel bir sınıf olduğu için kendine özgü bazı şeyler de yapabilirler, mesela ne olabilir ? Diyelim ki istimlak etme yetkileri olsun, ve verilen parsel no’sundaki araziyi istimlak etsinler.

 

public class TapuMemuru:Memur

      {

            public TapuMemuru()

            {

            }

            public void istimlaket(string parcelno)

            {

                  Console.WriteLine(parcelno + " nolu arsa istimlak edilmiştir.");

            }

      }

 

Bu şekilde tanımlanan tapumemurunun artık yeni bir özelliği var, ve istediğimiz arsayı istimlak edebiliyor. Böylece önceden gelen özelliklere yenilerini de ekleyebiliyoruz.

Kodumuzu çalıştırırsak:

 

TapuMemuru muzaffer=new TapuMemuru();

                  Console.WriteLine("Benim adım " +muzaffer.getisim());

                  muzaffer.raporyaz();

                  muzaffer.telefonet("555 0945");

 

                  muzaffer.istimlaket("456");

(bu örneği muzaffer_istimlak.cs dosyasında bulabilirsiniz. )

Muzaffer artık istimlak işini de yapacaktır.

 

Bakın son derece basit bir örnek olmasına rağmen bize sahip olduğumuz olanağa dair gayet güzel bir ipucu sunuyor. Sınıfları tanımlayıp, bu sınıflara çeşitli davranışlar atadığımızda, kalıtım ile bu davranışları taşıyabiliyoruz. Peki sadece bununla mı sınırlıyız ? Hayır, diyelim ki tapu memurları kendi bulundukları daireye göre rapor yazıyorlar. Yani her devlet dairesindeki memur aynı raporu yazmıyor. Bu durumda biz de sınıfımızı bu yeni olanağı destekleyecek şekilde tasarlayalım.

 

public class TapuMemuru:Memur

      {

            public TapuMemuru()

            {

            }

            public void raporyaz()

            {

                  Console.WriteLine("Tapu memurunun raporu: Ormanları kimseye vermiyoruz!!!");

            }

            public void istimlaket(string parcelno)

            {

                  Console.WriteLine(parcelno + " nolu arsa istimlak edilmiştir.");

            }

      }

 

Bakın burada raporyaz fonksiyonunu değiştirerek tamamen tapu memuruna özel bir fonksiyon elde ettik ve bu durumda:

 

                  TapuMemuru muzaffer=new TapuMemuru();

                  Console.WriteLine("Benim adım " +muzaffer.getisim());

                  muzaffer.raporyaz();

                  muzaffer.telefonet("555 0945");

 

                  muzaffer.istimlaket("456");

(bu örneği muzaffer_hata.cs dosyasında bulabilirsiniz. )

 

Kodunu çalıştırırsak, muzafferin artık farklı bir rapor yazdığınını görebiliriz. Ancak bu kodda bir hata var. Bu hata kodunuzun derlenmesini veya çalışmasını engelleyen bir hata değil, yanlış anlaşılmasın, sadece elimizdeki bazı olanakları buradaki basit kullanım ile harcıyoruz. Kalıtım, burada gördüğünüzden çok daha detaylı şekilde tasarıma olanak vermek üzere tasarlanmış bir özellik. Ve sadece bir davranışın kalıtımla taşınması veya değiştirilmesi değil, çok daha güçlü özellikleri destekliyor. Yani tapu memurunun türediği sınıf içinde tanımlanan bir fonksiyonu burada basitçe yeniden yazarak yeni bir işleyiş kazandırmanın birden fazla yolu var. Tabi birbirinden farklı bu yolların varolmasındaki amaç bize bir çok durumda esnek çözümler sağlamak. Bu arada unutmadan ekleyeyim, C# ın bizlere sağladığı olanakların hepsini, temiz bir anlatım sağlayabilmek için yazdığım kodlarda kullanmıyorum, her ne kadar biraz vicdan azabı duysam da bu aşamada kodun anlaşılabilirliğini, kalitesinden daha önemli buluyorum.

Özellikle vurgulamak istediğim bir başka nokta ise şu: ecnebilerin “Code Reuse” yani kodun yeniden kullanılması anlamına gelen bu özellik her ne kadar bize kalıtım ile sunulsa da, hayatta her şeyin olduğu gibi kalıtımın da fazlası zarar!! İleride yazacağım kısımlarda neden zararlı olduğunu açıklayacağım, ama aklınızın bir köşesinde bulunsun diye şimdilik not düşelim: “Kalıtım tek başına kod’un yeniden kullanımında her şeyi çözen sihirli bir teknik değildir!!”

İsterseniz bu noktada size bu anlatılanları hazmetmek için biraz zaman verip, bir sonraki yazıda çokbiçimlilik, yani polymorphism ile devam edelim...

 

Şeref Arıkan

 

Not:Bu yazıda bahsettiğim dosyaları içeren zip dosyası için lütfen tıklayın.