Ülkemizde son zamanlarda gittiğim, konuştuğum pek çok yer ve kişide bazı nesneye yönelik programlama tekniklerinin önem kazandığını duyuyorum. Ancak yine pek çok yerde, bazı kendi projelerim de dahil olmak üzere, hala daha bazı başarısızlıklar hüküm sürüyor. Örneğin kullanıcı arayüzünün kolayca yapılan projelere adapte olamaması bunlardan biri.
Kullanıcı arayüzü katmanı gün geçtikçe projelerde daha fazla önem kazanmaya başlıyor. Hatta yeni teknolojilerle birlikte arayüz katmanında yapılabilecek şeyler çok daha artmaya ve güçlenmeye de başladı. Örneğin artık neredeyse web formlarımızı da sanki birer windows formmuş gibi kullanabiliyoruz hatta wpf teknolojisi ile birlikte çok daha zengin ve kolay tasarlanabilir windows formları yapabiliyoruz.
Ancak bu teknolojiler ne kadar ilerlerse ilerlesin, bunların bize sunduğu gücü uygun şekilde yönlendirip ihtiyacımız doğrultusunda kullanamazsak bize kolaylık sağlamasının aksine işimizi çok daha zora sokabilir ve yaptıklarımızla kodlar içerisinde kaybolabiliriz. Güçlü trendlerden biri olan Test Driven Development (TDD)’ ı uygulamamız da bir o kadar zor olacaktır.
Aslında önermeye çalıştığım modeli biraz daha anlatabilmek için basit bir diyagram üzerinden gitmeyi faydalı görüyorum;
Yukarıda çizmeye çalıştığım diyagram aslında 90 lı yılların başında sunulmuş bir tasarım kalıbı olan MVP’ ye (Model-View-Presenter) aittir. Peki ya MVP nedir? MVP; bir kullanıcı arayüzü tasarım desenidir ve asıl amacı unit testing (bir sonraki yazımda bundan daha detaylı olarak bahsedeceğim) ile test edilebilir kullanıcı arayüzleri tasarlamak ve önceki yazımda da bahsettiğim “Seperation of Concern” kavramını arttırmaktır. Peki ya Model, View ve Presenter ne anlama gelmektedir? Model, ekranda gösterilen veriyi tanımlayan bir arayüzdür. View, Kullanıcı arayüzünde modeli gösteren ve kullanıcı aksiyonlarını presenter (sunum) katmanına gönderen ve bu modelin değişikliğini yöneten katmandır. Presenter ise modelden aldığı verileri formatlayıp, modifiye edip view üzerinde gösterilmesini sağlar.
Peki ya böyle bir yapıyı kendi projelerimde nasıl uygulayabilirim? Ya da daha önemlisi elimdeki mevcut bir projeyi bu yapıya uygun hale nasıl getirebilirim? Bunun için basit bir senaryoyu ele alalım. Bir tasarım yaptınız ve projenizi tamamladınız. Ancak yeni versiyonunda MVP gibi bir tasarım desenini kullanarak yaptığınız uygulamayı daha iyi test edilebilir ve böylece daha stabil bir hale getirebilirsiniz. Hatta zaten elinizde yeni versiyonda yapacağınız her değişiklik ya da yenilik bir doküman halinde varsa ve bir takım kuralları zaten önceden kestirebiliyorsanız, TDD yaparak çok daha hızlı ve güvenli bir şekilde uygulamanızı geliştirebilirsiniz. Şimdi de basitçe bir takım konseptlerden bahsedip ardından sözünü ettiğim MVP tasarım desenini uygulamaya çalışcağım.
Şimdi öncelikli olarak basit bir senaryo olan kişi detay formunu ele alalım. Bir tane sayfamız olsun ve ilgili sayfamızda gelen kişiye ait olan bir takım verileri yükleyelim. Bunu yapabilmek için öncelikle asp.net sayfamızı, yani view imizi hazırlamaya koyulalım. Öncelikle IPersonView isminde bir arayüz hazırlıyorum ve burada kişi formunda olmasını beklediğim bir takım özellikleri de tanımlıyorum.
Böylece view için gerekli arayüzü hazırlamış olduk. Bundan sonra bir presenter ile işlemleri yönetmemiz gerekiyor. Ancak burada dikkat etmemiz gereken bazı noktalar var. Öncelikle view üzerindeki verileri doldurabilmek için parametrik olarak bir yerlerden bazı değerler yüklemem gerekiyor. Dolayısıyla bir veritabanı üzerinden ya da bir dosya üzerinden bir şekilde bu verileri yüklemem gerekiyor ardından yine bir yöntem ile bu yüklediğim verileri bir entity nesnesine doldurup ardından IPersonView arayüzü ile eşleştirip web sayfam üzerinde değişiklikleri göndermem gerekiyor.
Dikkat ederseniz, burada birden fazla iş var. Ancak bunların hepsini bir presenter yapısının hazırlaması ve yönetmesi gerekiyor. Ancak tabi ki yine buradaki işlemleri parçalara bölmeye çalışacağız. Mümkün olduğunda çok parçaya bölmemiz bize ileride çok ciddi kolaylık sağlayacaktır. Tabi burada neden bir entity nesnesine doldurduğumu sorabilirsiniz. Bunun nedeni arka tarafta yapılan veritabanı işlemlerini soyutlaştırıp, araya bir entity katmanı eklemek. Böylece veritabanı işlemlerini yöneten ayrı bir katman daha sağlamış oluyorum. Bunun bana faydası da elbette merkezi noktalar dışında veritabanı işlemi yapmamış olmak oluyor.
Peki öyleyse işe koyulup kodu yazmaya devam edelim. Ne demiştik;
a. İstediğimiz kişiye ait olan kayıt için entity leri veritabanından yükleyecek bir sınıfa (IPersonOperator)
b. Bize kişi entity sini veritabanından yükleyecek olan bir sınıfa (IPersonDataMapper)
c. Yüklenen entity ile view arasındaki ilişkiyi tutan ayrı bir sınıfa (PersonViewPresenter)
ihtiyacımız olacak.
Biraz daha önceden deneyimleme şansım olduğu için ve daha anlamlı olması açısından en içten başlayıp öncelikle IPersonDataMapper arayüzünü ve bunu implemente eden sınıfı tasarlarayak işe başlamak istiyorum.
Yukarıdaki kod örneğinden de görülebileceği gibi IPersonDataMapper arayüzü veritabanından IPerson tipinde bir sınıf yükleyeyip bunu bir üst kısıma vermek görevini üstlenmiş durumda. Bunu yaparken de kullanıcıdan bu kaydı yükleyebilmek için bir primary key değeri bekliyor. Peki bunu yapan sınıf nasıl bir davranış sergiliyor,
Bu sınıfın amacı az önce de söylediğim gibi veritabanına sorgu çekmek ve veritabanından çektiği sorgu sonucunda ulaştığı bir IPerson nesnesi yüklemek. Bu işlem DatabaseHelper sınıfında yapılmaktadır. DatabaseHelper sınıfı önce loadFromDb methodu ile birlikte veritabanından bir DataTable çeker, ardından bu DataTable’ı Person nesnesinin içini doldurmak için kullanır. Buradaki DatabaseHelper sınıfı sadece veri üretmek için eklenmiştir ve bu örnekte hiç bir veritabanı işlemi yapmamaktadır. Basit bir şekilde hard coded olarak bir datatable üretir ve bunu nesneye yükler. Ardından reflection ile Person nesnesinin tüm property lerini gezer ve bunların hepsini datatable ın bir satırından doldurur. (Detaylı incelemek için kod örneğine bakabilirsiniz.) Bu sınıf bizim katmanlarımızın en alttakini oluşturmaktadır. Bu katman veri erişim katmanı ile direk iletişime geçer ya da arada bir entity katmanı kullanır. Yukarıdaki örnekte veri erişim katmanına direk erişim sağladığı düşünülmüştür. Ancak tabi ki burada NHibernate vs. gibi ORM araçlarını da kullanabilirsiniz. Son olarak bu sınıfı ve arayüzünü kendi projelerinizde tasarlarken dikkat etmeniz gereken şey sadece ihtiyaç duyduğumuz methodları buraya eklemek olacaktır. Burasının sadece veri çekip bu verileri bir üst katmana taşıma görevini üstleneceğini ve elbette yalın kalması gerektiğini hatırlatmayı gerekli görüyorum.
Artık verileri çekebilece k bir sınıfımız mevcut. Bundan sonra yapmamız gereken şey veritabanından yüklenen nesneyi view entity mize dönüştürmek olacaktır. Bunun için hatırlarsanız yukarıdaki sıralamada IPersonOperator arayüzünü implemente eden bir sınıfa ihtiyaç duyacağımızı anlatmıştım. Peki bu arayüzde nelere ihtiyacımız var şimdi ona bir göz gezdirelim,
Bu arayüzde yapmamız gereken daha doğrusu üstlenmemiz gereken görev, elimizdeki view i anlamlandırmaya yetecek olan minimum sayıdaki veriyi kullanarak PersonView PersonViewPresenter nesnemizin anlayabileceği şekilde bir çıktı üretmek. Burada anlamlandırabileceğimiz çıktı bir PersonEntity nesnesi. Birazdan bu nesnenin içeriğini de inceleyeceğiz ancak öncelikle söylemeden edemeyeceğim, Operator sınıfın, DataMapper sınıfından gelen veriyi anlayıp buna dönüştürmesi gerekmektedir. Bu yüzden de birisinin operator sınıfını yaratırken hangi DataMapper sınıfını kullanarak işlem yapacağını bildiriyor olması lazım. Böylece ilerleyen zamanlarda DataMapper yöntemi değişirse, yani veriyi alacağımız kaynak farklılaşırsa bu sınıfımız bu değişiklikten etkilenmemiş olacaktır.
Yukarıda az önce bahsettiğim şeyi görebiliyoruz. Yani ben burada hangi DataMapper nesnesi kullanacağını constructor’ında tanımladım. Ancak siz bunu bir konfig urasyon dosyasından almak da isteyebilirsiniz. Bu yüzden bu kısmın ucu tamamen açık bırakıldı. Ayrıca dikkat ederseniz LoadPerson methodunda yapılan şey, tamamen amacına uygun. Yani Operator sınıfı DataMapper sınıfına hangi veriyi alacağını bildiriyor ve karşılığında bir IPerson nesnesi alıyor ve daha sonra da bunu PersonEntity nesnesine dönüştürüyor. Böylece bir üst katman olan yani view öncesinde view den gelen operasyona göre veriyi view e yükleyecek olan PersonViewPresenter nesnemiz için de herşey hazır hale gelmiş oluyor. Bundan sonra Presenter katmanımız PersonEntity nesnesini alacak ve kendi bildiği IpersonView nesnesine ilgili alanlarına dolduracak. Bu işlemin nasıl yapıldığını yine aşağıdaki kod parçasında görebilirsiniz.
Burada dikkat etmemiz gereken bir kaç nokta var. PersonViewPresenter nesnesi yapıcı methodu içerisinde bir tane IPersonView instance ına ihtiyaç duymakta. Yani Presenter’ın görevini yerine getirebilmesi en az bir view e ihtiyacı var. Bunun yanında bir de operator sınıfına ihtiyacı var. Ancak elbette az önce söylediğim gibi bu operator sınıfı bir konfigurasyon üzerinden yüklenebilir. Daha sonra LoadPersonDetail() methodu ile birlikte presenter verileri operator sınıfı üzerinden yüklemekte. Burada bir de view üzerindeki SelectedPersonId property si kontrol ediliyor. Buradan gelen değere göre ilgili operator sınıfına primary key veriliyor ve o da gidip kendi bildiği şekilde veriyi okuyor. Dikkat ederseniz burada presenter verinin nereden okunduğu konusunda bilgi sahibi değil. Yani okunacak olan veriden tamamen soyutlanmış. Veritabanı işlemlerine de karışmıyor. Onun ilgilendiği tek şey bir adet PersonEntity nesnesi. Bu durumda bu sınıfımız sadece gelen entity nesnesini kendi bildiği view üzerine eklemekle yükümlü. Eğer sayfamız üzerinde bind edilmesi gereken başka nesneler olsaydı (GridView, TreeView, DropDownList vs. ) onların da yüklenmesi işlemi yine bu presenter tarafından operator sınıf üzerinden yapılacaktı. Elbette bu durum bizim hazırladığımız DataMapper sınıfını da etkileyecek bir değişiklik olacaktı. Bununla ilgili örneği bir sonraki yazımda yapmaya çalışacağım.
Artık elimizde herşey hazır, tek yapmamız gereken yeni bir view tasarlamak. Bunun için yaptığım şey basit bir asp.net web application hazırlamak oldu. Daha sonra ilgili view arayüzümüzü bu sayfa üzerinde implemente ettim. Daha sonra bu pattern’ın bize sağladığı en güzel şey olarak bir benzerini de windows forms üzerinde yaptım. Ve kodun diğer hiç bir tarafına dokunmadan yine aynı view i bir windows form üzerinde implemente ettim ve yine çalıştığını gördüm. Öncelikle asp.net formumuzu inceleyelim,
Burada yaptığımız şey hepsinden çok daha basit. Sayfamız initialize olurken, bir tane presenter yaratıyoruz. Bu yarattığımız presenter’a da sayfamızın kendisini veriyoruz. Bu durumda artık presenter katmanımız hangi view üzerinden işlem yapacağını biliyor. Daha sonra SelectedPersonId property’sinin getter’ında seçilen kullanıcı numarasını alabilmesi için query string üzerinden yükleyebilmesi için gerekli şekilde bir hazırlık yapıp sayfanın Page_Load olayında bu işlemi tetikliyoruz. Dikkat ederseniz burada yazdığımız kod neredeyse yok. Sayfamız ne nasıl şekilde verileri yükleyeceğini biliyor, ne de yüklenen verileri nasıl ve nereye eşitleyeceğini biliyor. Tüm bilgi presenter üzerinde saklanıyor. Ayrıca burada yine dikkat ederseniz presenter katmanımız da hangi view i yüklediğinden habersiz çalışıyor. Peki bu tam olarak ne anlama geliyor? Şu anda bir asp.net web form’unu yükledik. Ancak ilerleyen zamanlarda tek bir satır kod değişikliği yapmadan bunu aynı şekilde bir windows formunu yüklemek için de kullanabiliriz. Ayrıca burada yaptığım örneğe ait bir solution dosyasını buradaki link’e tıklayarak edinebilirsiniz. Bu örneğin içerisinde bu makalenin tüm kodları asp.net ve windows forms üzerinde uygulanmış örnekleri yeralmaktır.