Sunday, October 30, 2005

Nesne Tasarımı

Java veya diğer dillerde kodlama eylemi nesne etrafında dönmektedir. Merkezi yer tutan ve en küçük veri yapısını teşkil eden nesnelerin tasarımı, temiz bir mimari için çok mühim bir olaydır.


Bu yazıda, nesne tasarımı hakkında bazı dersleri paylaşacağız.

Tek Eylem Nesneleri

Bu tür nesneler, tek bir işlem çevresinde yazılmıştır. Turnosol testi olarak 'bu nesnenin sistemdeki rolü nedir' sorusuna, 'bu nesne X yapar' cevabı alınmasıdır.

Halbuki bir nesne, çok özel şartlar olmadıkça, birden fazla işlemin toplamı olmalıdır. Ünlü nesnesel tasarımcı Bertrand Meyer, bir nesneyi üzerinde düğmeler (işlemler), okuma göstergeçleri (sorgular) ve anlık durumu (state) olan bir makinaya benzetir.

Örnek olarak bilgisayar bilimden yakınen bildiğimiz yığıt (stack) nesnesini gösterebiliriz. Yığıtta, üst noktayı işaret eden göstergeç bilgisi, kaç eleman olduğuna dair bir sayaç, ve eleman ekleyen/çıkartan işlemlerden oluşan bir yığıt sınıfı vardır. İhtiyaca göre kullanıcı program, bu yığıt işlemlerini istediği sırada çâğırabilir. (Tabii nesnenin iç bütünlüğünü bozmadıkça, mesela boş bir yığıttan eleman çıkartmaya çalışmak gibi).

Buna kıyasla, tek bir işlem etrafında yazılmış nesnelere şüphe ile bakmak gerekir. Bu tür nesneler, tasarımcısının işlevsel geçmişini yansıtıyor olabilir.

Bir nesneyi, durumu, alışveriş listesi halindeki işlevleri, ve anlık durumu olan bir makina olarak görmeye başlamak lazımdır.

Yan etkisi olan sorgular

Yan etkisi olan sorgulardan kaçınılmalıdır. Mesela,

public class Yığıt {
public void ekle(Object o) { .. }
public Object siradakiEleman() {
.. elemani bul
.. yigittan cikart
return nesne;
}
..
}

Bu örnekteki siradakiEleman sorgusunun yan etkisi var, çünkü, hem sıradaki elemanın ne olduğunu söylemekte, hem de bu elemanı yığıttan çıkartmaktadır. Bu sebeple aynı sorgu iki kere üst üste çâğırıldığında, aynı sonuç gelmeyecektir.

Bu tür bir sorgu, o sorguyu taşıyan nesneyi kullananlar için karışık bir durum yaratır. Makina örneğine dönersek, bir termometreye baktığınız ve sıcaklığı okuduğunuz her seferde sıcaklık değerinin değişmesi nasıl olurdu? Çok karışık olurdu değil mi? Nesneler üzerindeki sorgu tasarımı da böyledir. Kullanıcı programcılar açısından üst üste ne kadar çağırılsa da, sorgu çağırımının, nesnenin anlık durumunu değiştirmeyecek şekilde yazılmış olması, tasarım açısından çok önemlidir.

Yukarıdaki nesne, şu şekilde değiştirilmelidir.

public class Yığıt {

public void ekle(Object o) { .. }

public Object siradakiEleman() {
.. elemani bul
return nesne;
}
public void siradakiElemaniCikart() {
.. elemani bul
.. yigittan cikart
}
..
}

Anlık Nesneler

Nesnesel teknoloji kullanan projelerde, programcıların çoğunun programın çalışması esnasında anlık hafızaya alınıp hemen atılacak türden nesneleri, her nedense nesneden saymadığını görüyoruz.

Meçhul bir sebepten dolayı, nesnelerin illâ ki uzun süre hafızada kalması gereken şeyler olduğunu zanneden programcı arkadaşlarımız var. Burada herhalde sorumlu olan, nesnesel tasarımı sürekli gerçek dünya nesnelerine bağlayarak anlatan, benzetimsel bir hava içinde sunan belgeler olmuştur.

O zaman dersimiz şöyle olabilir: Bir nesneyi hafızaya yükleyip orada uzun süre tutmak ile, nesneyi yükleyip gereken işlemleri çağırıp hemen atmak arasında, bilgisayar donanımı için hiç bir fark yoktur. Eğer her iki şart altında da işlem1, işlem2 ve işlem3 çâğırıldı ise, nesnemizin anlık durumu işlem3'ten sonra her iki şartta da aynı olacaktır.

Galiba, hemen yüklenip, kullanılıp, atılan nesneler programcılara Tek Eylem Nesnesi gibi geliyor. Bu yanlış bir saptamadır. İşlem1, işlem2, işlem3'ten bahsettiğimizi hatırlayınız. Bu işlemleri arka arkaya çağırdık, fakat diyelim ki 1 milisaniye içinde çâğırdık. Ama nesne açısından 1 milisaniye içinde 3 işlem câğırılması ile, 10 saniye içinde aynı işlemlerin çağırılmasının hiç bir farkı yoktur.

Bu yüzden, bu şekilde çabuk yüklenen/atılan nesneleri kullanın. Yardımcı nesne olmaları açısından mimarinize temizlik katacaklardır.

Sürekli Gönderilen Bildirgeç

Eğer nesneninizdeki işlevlere gönderilen bildirgeçler, sürekli aynı bir bildirgeç tipini taşıyorsa, bu tanımlanmamış yani eksik olan bir nesnenin göstergesidir. Mesela aşağıdaki gibi bir nesne düşünelim:

public class Isleyici {

public void birIslem() {
}

public void baskaBirIslem() {
}

public void ekle(Object bunu, Vector bunaEkle) {
...
...
}

public Object cikart(Vector bundanCikart) {
...
...
}

...
...

}

Gördüğümüz gibi aynı Vector nesnesi sürekli olarak Isleyici nesnesinin işlemlerine gönderiliyor. Niye? Üzerinde işlem yapılsın diye. Fakat, bize göre bu tür bir nesne tasarımı, işlevsel tasarım kokuyor, nesnesel değil. Sürekli tekrar eden bildirgeçler, eksik olan bir nesnenin işareti ise, demek ki, Vector'ü kapsayan (encapsulate) etmesi gereken bir nesne eksik.

Nesneler, anlık durumları olan ve bu durumu hatırlayabilen kavramlardır. Bu özelliği kullanalım: Vector gibi bir değeri sadece bir kere gönderip, bu Vector'ün bu yeni kapsayan yeni nesne tarafından muhafaza edilmesini sağlamalalıyız. Bu yapıldıktan sonra, artık sonraki bütün işlemler içerde tutulan Vector üzerinde yapılacaktır, yani Vector'ün bir daha geçilmesine gerek kalmaz.

//
// yeni sınıf
//
public class VectorYığıtı {

protected int tepeIndisi = ...;

protected Vector vector = null;

public VectorYığıtı(Vector v) {
vector = v;
}

public void ekle(Object eleman) {
vector.add(eleman);
}

public Object cikart() {
...
return vector.get(tepeIndisi);
}
}

public class Isleyici {

public void birIslem() {
}

public void baskaBirIslem() {
}
}

İşte bu kadar! Böylece İşleyici nesnesi daha anlaşılır hale geldi, ve yeni VectorYığıtı nesnesi çok daha temiz.

No comments: