Neden Nesne-Merkezli Programlama? II
“Neden Nesne Merkezli Programlama” başlıklı dizinin ilk yazısında, nesne-merkezli dillerin gerçekliği ifade etme noktasında, yordamsal dillere göre nasıl bir avantaja sahip olduklarını açıklamıştık. Buna “ifade gücü” demiştik. Bu dizinin ikinci yazısında da, ifade gücünü bir örnekle açıp, sonrasında da “değişim” konusunu ve bu açıdan nesne-merkezli dillerin neler sağladıklarını ele alalım.
Örneğimiz şöyle: Düşünün ki üniversitelerin bölümlerindeki ders planları ve öğrencilerin derslere kayıt işlerini yöneten web tabanlı bir “Üniversite Öğrenci Bilgi Sistemi” geliştirmek durumundayız. Bu yazılımın, öğrencilerin derslere kayıt sürecini içeren kısmını yordamsal bir yaklaşımla nasıl tasarlayıp programlayacağımızı ele alalım. Derslere kayıt sürecinin, “Derse kayıt ol” isimli bir kullanım şekli (use case) ile detaylandırıldığını varsayalım. Buna göre öğrenci, bu kullanım şeklini işletebilmesi için zaten sisteme girmiş (logged on) durumda olmalıdır. Öğrenci, sonrasında alabileceği derslerin listelendiği sayfaya gelmiştir. (Bu sayfaya doğrudan gelebileceği gibi arama yaparak da gelmiş olabilir.) Bu sayfada almak istediği dersleri seçer ve bir tıklama ile bu isteği, tarayıcıdan sunucuya gönderir. Kullanım şekli, sürecin sahip olması gereken ders seçme ve kayıt olma ile ilgili iş kurallarını ifade etmiştir ve sürecin sonunda, sistemin bu kuralları yerine getirip, sonrasında da kaydolunan dersleri göstermesi, kaydolunamayanlar varsa, onları da sebepleri ile listelemesi gereklidir. Bu listelemenin de, sunucunun, tarayıcıya gönderdiği ve sürecin en son arayüzü olan bir sayfada olduğunu da varsayalım.
Yukarıdaki ihtiyaçların tasarımı ile ilgili odaklanmamız gereken nokta şu: Sistem, ders seçme ve kayıt olma ile ilgili iş kurallarını nasıl yerine getirir? Dolayısıyla bu bir tasarım hatta sonrasında da bir kodlama sorusu. İsterseniz önce iş kurallarından bahsedelim biraz. İş kuralları (ya da business rules), sürecin farklı noktalarında, akışın keyfiyetiyle ilgili şeyler söylerler bize. Bazen akışta kısıtlamalar yaparlar bazen de başka fonksiyonları ya da akışları tetiklerler. İş uygulamaları gittikçe daha karmaşık hale geliyorsa, iş kurallarının sayısı ve içindeki öğeleri ve aralarındaki ilişkileri de artıyor demektir. Bu uygulamada da, lisans, yüksek lisans, tam-zamanlı, yarı-zamanlı, gündüz öğretimi, gece öğretimi öğrencisi gibi farklı öğrencilerin farklı iş kurallarına uygun olarak ders seçtikleri ve derse kayıt oldukları varsayılabilir. Örneğin lisans üstü öğrencilerin ders seçme sürecinde, derslerini, danışman hocasının onaylaması, bir adım olarak bulunabilir. Benzer şekilde derslerin de, lisans ya da yüksek lisans dersi, zorunlu ders, seçmeli ders vb. farklı tiplerini olduğu, derslerin önşart derslerinin olduğu, sınıf kapasitesi ya da seçmede belli öğrencilere öncelik vermekle ilgili gibi pek çok iş kuralına sahip olduğu düşünülebilir.
Şimdi, yukarıdaki soruyu biraz daha net olarak şu şekilde sorabiliriz? Öğrencilerin farklı tiplerine göre farklı seçme süreçlerini, derslerin de farklı tiplerine göre farklı kayıt süreçlerini ve bu süreçlerdeki iş kurallarını, yazılım sisteminin hangi parçası bilir ve yerine getirir? “Süreçlerdeki her bir adımı ve iş kuralını yerine getiren bir fonksiyon yazarım” dediğinizi duyar gibiyim? Peki o zaman sorumu şöyle sorayım: Bu fonksiyonları nereye koyarsınız? Yani ortaya çıkan, örneğin toplam 100 tane olan fonksiyonu nasıl bir organizasyona tabi tutarsınız? “Birbirleriyle ilintili fonksiyonları bir araya getiririm” diye cevap vereceksiniz büyük bir ihtimalle. Bu durumda iki sorum olur: Bir, “fonksiyonların ilintili olması” ne demektir? Yani neye göre ilintiye karar verirsiniz? İki, o birbiriyle ilintili fonksiyonların ilişkisini nasıl ifade edersiniz?
Fonksiyonların birbirleriyle ilintili ya da ilişkili olmasına, örneğin aldıkların parametrelerin bazılarının aynı olması, dolayısıyla ortak bir parametre seti üzerine çalışmalarına bakarak karar verebilrisiniz. Yani f() metodu a, b ve c parametrelerini, g() metodu da a, c, e ve f parametrelerini, u() metodu da b, c, ve f parametrelerini kullanıyorlarsa, bu metotların birbirleriyle ilintili olduklarını dolayısıyla aynı “yerde” tanımlanmaları gerektiğini soyleyebilirsiniz.
Peki ilintili olmayı hallettik. “Birbirleriyle ilintili fonksiyonları bir araya getiririm”den ne anlıyoruz? Muhtemelen, ilintili fonksiyonları aynı dosyaya koymayı, değil mi? Peki neden iki dosyaya koymazsınız? Ya da neden koyarsınız? Demek istediğim, kaç dosyaya koyduğunuzun hiç bir önemi yok, çünkü bir dosya olsun iki tane olsun, iş problemini çözmek açısından hiçbir öneme sahip değiller. Bir dosya ya da iki dosya ne farkeder ki iş alanımız, süreçlerimiz ya da iş kurallarımız açısından? Dolayısıyla ilintili fonksiyonları ve hatta onların üzerinde çalıştıkları parametre setini aynı dosyada tanımlamamızın, iş sürecimize hiç bir katkısı yok, çünkü “dosya” kavramı, bu anlamıyla işle ilgili bir soyutlama değil. Böyle bir durum olsa olsa üzerine farklı programcıların çalışmasını sağlamak açısından bir fayda yaratabilir. Bu fonksiyonlar, makinamızdaki IDE’de ya da kod depomuzda farklı dosyalarda tutulur ama iş süreçleri açısından bu durumun hiçbir önemi yoktur. Brooks babanın dediği gibi olsa olsa “accidental” yani arizi, tamamen ikincil bir problemi çözer, “iş”imizi halletmekle ilgili en ufak bir katkısı yoktur. Bunun sebebinin, yordamsal dillerde, ilintili fonksiyonları, bir üst katmanda bir araya getirecek bir yapının olmamasıdır. (Bazı dillerde “namespace” ya da “package” gibi yapıların olması bu durumu değiştirmez çünkü bu yapılar genel olarak erişim kontrolü için kurgulanmışlardır.) Nesne-merkezli dillerde ise yukarıdaki duruma şöyle bir cevap verilir: Fonsiyonlar, bir nesnenin sorumlulukları olarak ele alınır ve o nesne kendisiyle ilgili sorumlulukları yerine getirdiği gibi, o sorumlulukların üzerinde çalışacakları parametreleri yani veri yapılarını da kendi içinde barındırır. Dolayısıyla “ilintili” olmak kavramı nesne ile anlam kazanır ve fonksiyonlar ile o fonksiyonların üzerinde çalıştığı veri yapılarını bir nesne yapısıyla bir arada ifade etmeye de bohçalama ya da kapsülleme (encapsulation) adı verilir. Yordamsal dillerde de özellikle soyut veri tiplerini (abstract data type) ifade etmek amacıyla bazı bohçalama yapıları bulunsa da bu yapıların, nesne-merkezli dillerin nesne kavramı kadar güçlü olmadığı aşikardır. Bohçalama, nesne-merkezli dillerde genel olarak sınıf (class) yapısıyla oluşturulur. Nesneler ise sınıflardan üretilir ve her nesne (şimdilik) bir sınıfın, bellekte yaşayan bir örneğidir (instance). Dolayıısyla aynı sınıftan üretilmiş nesneler aynı tiptedirler yani aynı değişkenlerden oluşan duruma (state) ve sorumluluklara yani (functions ya da Smalltalk ve Java’cı diliyle “method”) sahiptirler. Aynı tipte olan nesnelerin durumunu oluşturan veri yapıları da aynıdır ama durumun kendisi farkıdır çünkü durumu oluşturan değişkenlerin aldıkları değerler, nesneden nesneye değişir. Yukarıdaki örnekle açıklarsak, öğrenci nesneleri muhtemelen, Ogrenci sınıfında tanımlanır ve bu sınftan türetilirler. Tckn, isim, soy isim vb. bilgiler, Ogrenci sınıfında tanımlanan değişkenlerle ifade edilirler. Dolayısıyla bu bilgilerin tümüne durum dersek, bellekte durumları aynı olan birden fazla nesne olmamalıdır, çünkü öğrenci nesnelerinin en azından tckn bilgisi birbirlerinden farklıdır. Benzer şekilde aynı Ogrenci sınıfından türetilen tüm öğrenci nesneleri de aynı sorumlulukları yerine getirirler, çünkü hepsi Ogrenci sınıfında tanımlanan metotlara sahiptirler. Sadece o metotlar, genel olarak nesnenin durumu üzerine çalıştıklarından, farklı nesneler için aynı sorumluluğu farklı durumlar üzerinden yerine getirirler.
Görüldüğü gibi, nesne/sınıf kavramı ve yapısı, fonksiyonlardan bir yukarıda bir soyutlama yapısı olarak görülebilir ve gerçek dünyadaki fiziksel ya da kavramsal şeyleri ifade etmemize yarar ki bunun da iş süreçlerini modelleyip kodlamamız açısından önemi çok büyüktür.
İfade gücünü, bir örnekle açıkladıktan sonra, nesne yapılarının, değişim konsunda bize nasıl imkanlar sağladıklarını, bir sonraki yazıda ele alacağız.
Toplam görüntülenme sayısı: 1563