Yazan : Şadi Evren ŞEKER
Bu yazının amacı, nesne yönelimli programlama ortamlarında kullanılan bir tasarım kalıbı (design pattern) olan gözlemci tasarım kalıbını (observer design pattern) açıklamak ve kullanımına dair bir örnek vermektir. Nesne yönelimli olmayan programlama dillerinde (örneğin C) aynı yapı, geri çağırım (callback) ismi verilen yaklaşım ile yapılabilir.
Konuya, klasik bir gözlemci tasarım kalıbını açıklayarak başlayalım. Geliştirme ortamı olarak sıklıkla kullandığım Netbeans içerisinde bir observer pattern eklenmesi halinde aşağıdakine benzer bir sınıf diyagramı (class diagram) görülmektedir:
Yukarıdaki kalıpta görüldüğü üzere 4 temel unsur bu kalıbı oluşturmaktadır:
- ConcreteObserver : Somut bir gözlemci
- ConcreteSubject: Somut bir fail (özne)
- Subject : Somut failden (özneden) türemiş olan diğer failler
- Observer: Somut gözlemciden türemiş olan diğer gözlemciler./
Gözlemci tasarım kalıbı, yukarıdaki şekilde görülebileceği üzere, aslında bir Fail / Meful ilişkisi (subject / object, özne /nesne ) üzerine kuruludur. Somut fail ile kastedilen, gözlemci yapısındaki herhangi bir fiile sebep olan (eylemi yapan) faildir (özne). Buna karşılık, nesne yönelimli programlama dillerinde, üzerine bir fiil uygulanan şey genelde nesne olarak ifade edilir. Yani zaten nesne yönelimli dillerde herhangi bir fiil, sadece nesneler üzerine uygulanmaktadır. Bu durumda fail ile kastedilen, yukarıdaki kalıpta concreteSubject ve bu yapıdan türeyen bütün subject sınıfı nesneler, meful ise bu nesnelerin metotlarına parametre olarak geçirilen ve üzerinde işlem yapılan nesnelerdir.
Bu yaklaşımı bir kod örneği üzerinden açıklamaya çalışalım. Öncelikle tasarımımızın sınıf diyagramını (class diagram) vererek konuya başlayalım:
Yukarıdaki şekilde, modelimizde bulunacak olan üç unsur içinde, yani gözlemci, fail ve meful için birer sınıf tanımı yaptık. Bu sınıfların kodu aşağıda verilmiştir:
İlk olarak gözlemci sınıfı (class) ile konuya başlayalım. Sınıfın tanımında, bir main fonksiyonu bulunuyor ve bu main fonksiyonu aslında fail , meful ve gözlemci kavramlarının bir araya geldiği nokta oluyor. Zaten sınıf diyagramında da görüleceği üzere gözlemci bu iki kavramı bir araya getirir. Buna göre fail ve meful sınıflarından üretilen failNesne ve mefulNesne nesneleri (objects) (kodun 20. ve 23. satırlarında), kodun 26. satırında, addObserver fonksiyonu ile birbirine bağlanmıştır. Bu basit işlem, meful üzerinde bir failin gözlemini tanımlar. Ayrıca kodun 29. satırında klasik thread üretmek için kullanılan ve Runnable arayüzü üzerinden Thread çağırımı ile failNesne’mizi bir thread şeklinde başlatıyoruz.
Gelelim Fail sınıfının tanımına:
Yukarıdaki kodda, basit bir şekilde, kulanıcıdan, ekranda yazı okumak için BufferedReader, InputStreamReader’a ve InputStreamReader da System.in’e bağlanmış. Bu sayede, System.in üzerinden girilen bir veri, BufferedReader tarafından okunabilecektir. response ismindeki String değişkeni (variable) bir sonsuz döngü içerisinde (21. satırdaki while) kullanıcıdan veri okumakta ve sırasıyla setChanged() ve notifyObservers() fonksiyonlarını çağırmaktadır. Buradaki notifyObservers fonksiyonu aslında bütün bu yazının kalbini oluşturuyoru. Dikkat edilirse bu fonkisyona klavyeden okuduğumuz response isimli değişkeni parametre olarak veriyoruz.
Kodda dikkat edilecek diğer iki husus ise, Observable sınıfını miras almamız (inheritance) ve bir thread olarak çalışmasını istediğimiz için Runnable arayüzünü (interface) implements etmemizdir.
Son olarak meful sınıfını açıklayıp, kodun çalışmasına bakalım:
Kodda görüldüğü üzere, Meful isimli sınıfımız, basitçe Observer arayüzünü kendi üzerine uygulamıştır (implements).
Meful sınıfının yegane fonksiyonu update fonksiyonudur ve parametre olarak bir Observable nesnesi bir de arg isimli Object tipinde (yani JAVA açısından tipsiz) bir parametre almaktadır.
Burada tipsiz tipler ile ilgili aşağıdaki yazıyı okumakta yarar olabilir
Son olarak update fonksiyonu içerisinde alınan mesaj, tip inkılabı (typecasting) ile Object tipinden String tipine inkılap ettirilmiş ve ardından ekrana basılmıştır.
Şimdi yukarıdaki senaryoyu toparlayacak olursak, gözlemci sınıfımızdaki main fonksiyonu çalıştırılarak bütün senaryo başlamaktadır. Bu main fonksiyonu içerisinde bir fail bir de meful nesnesi üretilmekte ve üretilen nesneler birbirine addObserver fonksiyonu ile bağlanmaktadır. Yani artık meful sınıfından türeyen nesneler, fail sınıfının birer observer’ı olmaktadır. Ardından fail sınıfından üretilen nesne bir thread olarak çalıştırılmakta ve kendi içerisinde bulundurduğu, ve threadlerin ilk çalışan fonksiyonu olan run() fonksiyonunda tanımlı olan kullanıcıdan bir dizgi (String) okuma işlemini gerçekletirmektedir. Hemen ardından okunan mesajı, aslında hiçbir fikri olmayan ismini, ne tür bir parametre aldığını dahi bilmediği notifyObservers () fonksiyonuna parametre geçirmektedir. Bu fonksiyon, bir sevki tabii olarak meful sınıfındaki update isimli fonksiyonu tetiklemektedir. Update fonksiyonu ise kendisine parametre olarak gelen nesneyi String sınıfına çevirmekte ve ekrana bamaktadır.
Yukarıdaki kodu çalışması sonucu ekran görüntüsü aşağıdaki şekildedir:
Görüldüğü üzere kodumuz bir fiil sorusu sormakta ve klavyeden benim yazdığım “merhaba” yazısını ekrana geri basmaktadır. Buradaki mesajı okuyan fail isimli sınıftan türeyen nesne iken, ekrana cevabı yazan meful isimli sınıftan türeyen nesnedir
Yukarıdaki bu tasarım kalıbını, daha gerçekçi bir örnek üzerinden açıklamaya çalışalım.
Örneğin bir müzayede (açık arttırma) ortamını, gözlemci tasarım kalıbı ile modellemek isteyelim. Buna göre müzayedeyi yöneten bir kişi ve bu müzayedeye katılan müzayedeciler bulunacaktır. Buna göre müzayedeyi yöneten kişi bir gözlemci olmakta, müzayedecilerin her biri birer fail (fiili yapan kişi) olmakta ve müzeyede sırasında verilen her teklif (fiyat) bir meful (fiilin etkisindeki değer) olmaktadır.
Bu yaklaşımı aşağıdaki şekilde göstermeye çalışalım:
Yukarıdaki yaklaşımda, muzayede sırasında verilen değerler, gözlemcinin update fonksiyonuna birer parametre olarak geçirilmekte ve nihayetinde gözlemciden bağımsız olarak ilgili fonksiyonlar çağrılmaktadır.
Gözlemci tipi tasarım kalıbının bir dezavantajı, bu noktada çıkmaktadır. Gözlemci tasarım kalıbında, meful yapısında olan ve fiilden etkilenen nesnelerin sistemde etkin rol oynaması beklenir. Diğer bir deyişle, açık arttırmaya katılan her bir katılımcı, fail olarak fiyatlarını belirtirken, gözlemci bu durumu sadece gözlemekte ve bir fiilde bulunmamakta ancak fiileri gözlemektedir. Bu anlamda, gözlemci tasarım kalıbı, çekme iletişim modeli (pull interaction model) olarak düşünülebilir. Alternatifi olan itme iletişim modelleri (push interaction model) failin doğrudan eylemde bulunması ile sonuca ulaşmakta ve dışarıdaki nesneleri çalışmaya zorlamaktadır. Yani gözlemci örüntü modelinde, dışarıda bulunan nesnelerin çalışmaları sonuçları toplanmaktayken, itme iletişim modeli olan diğer kalıplarda, bir nesne, diğer nesnelerin sonuç üretmesini zorlayabilir. Burada, tasarıma bağlı olarak bir seçim yapmak gerekir.
Merhaba hocam,
Fonksiyon göstergeleri ile ilgili yazınızı okurken orada bir arkadaşımızın sorusuna denk geldim.(7 istatistik için if-else yapısı kullanmadan doğru nesneyi oluşturma ve işlemleri gerçekleştirme ile ilgili)
Siz de observer-design mantığı kullanarak çözülebileceğini söylemişsiniz. Ancak problemi okuyunca aklıma takıldı. Onun dediği 100 tane istatistik olsa 100 tane if-else yapmam gerekir sorununu bu mantıkla ve yaklaşımı kullanarak nasıl ortadan kaldırabiliriz bu benim kafamı kurcaladı ve yanıt bulamadım.
Şimdiden teşekkürler.
Kolay gelsinç
Soruya, ilgili yazının altında yorum olarak cevap vermiştim. Basitçe bir dizi oluşturulması gerekiyor. Dizinin kaçıncı elemanı çağrılırsa o fonksiyon çalışır. Yani kullanıcı 5 girdiğinde dizinin 5 elemanı olan fonksiyonu çağırmanız yeterli. Ayrıca bir if-else yapsına gerek kalmaz. Yazı ve cevabi yorum aşağıdaki adreste:
http://www.bilgisayarkavramlari.com/2007/12/18/fonksiyon-gostericileri-function-pointer/
başarılar
İyi aksamlar hocam,
Benim kafama bir olay takıldı.Örneğin kelimeBulundu observerımız var.Ve ben input’tan harf harf okuma yaparken boşluk olduğu anda bu observer’ın bunu farketmesini ve tutacağım bir count değerini günlemesini istiyorum.Bu yapıyı nasıl kurabilirim.Yardımcı olabilir misiniz acaba?Bu durumda benim her harften sonra notify demem mi gerekir,yoksa boşluk gelirse notify mı diyeceğim sonucta observable’ın yani fail nesnenin gözlemcisinin ne yaptıgını bilmemesi gerekir yanlış mı düşünüyorum?
Kesinlikle. Yani sizin fail (eylemi yapan, etken ) nesneniz, gözlemciyi besleyen değerler gönderir. Sizin gözlemciyi (observer) ne sıklıkla çağıracağınız ise (her karakterden sonramı her kelimeden sonra mı) sizin tasarım meselenizdir. Yani yukarıdaki daha önce yorum yapan arkadaşın sorusuna dönersek, bir metinden istatistik çıkarırken, kelime sayısı alan fonksiyonunuz varsa, bu durumda kelimelerden sonra fonksiyon çağıracaksınız. Şayet harflerden sonra çağıracaksanız bu durumda da notify işlemi harf sonunda yapılmalı.
Daha farklı bir yaklaşım olarak, kelime sonlarında notify edip, gözlemci nesnesine kelimeyi parametre olarak gönderebilir ve gözlemci kendi altında, harf işleyen ikinci bir fonksiyonu çağırabilir. Bütün bunlar sizin tasarımınız ile ilgili meselelerdir ve tek bir doğrunun olmadığı seçimin, probleme göre yapılması gerektiği durumdur.