Monthly Archives: September 2008

NHibernate

Nhibernate-1

Merhaba…Tahminimce bir kaç bölümden oluşacak yazı dizimizde Nhibernate teknolojilerini uygulamalı olarak incelemeye çalışacağız.Fırsat bulabilirsek ActiveRecord’a da değineceğiz. Basitçe bir web uygulaması yapıp Nhibernate’in çalışma mantığını irdeleyeceğiz.Basit uygulama deyince aklınıza sağda solda rastladığımız tercüme makalelerde geçen console uygulamaları gelmesin. Nesneler arasındaki ilişkilerin nasıl düzenlendiğini,performans karşılaştırmaları,sık rastlanan hataların ne anlama geldikleri en önemlisi web uygulaması içinde session yönetimine değineceğiz.
Nhibernate bildiğiniz gibi çoktan tarih olmuş relational database management systemlerin yerini alan object relational mapping araçlarından biridir.Javada kullanın Hibernate’in .net için yazılmış şeklidir.Sınıfların bir mapping dosyası ile veritabanında yer alan tabloların alanlarıyla ilişkilendirilmesi mantığına dayanır.Nhibernate’i buradan edinebilirsiniz.Ben Nhibernate 1.2.1.400 versiyonunu kullanıyorum.
Neden Nhibernate?
Çevrenizde “Eski köye yeni adet”,”Ben zaten bu işleri kendi veri sınıfımla yapıyorum,”datatable doldur boşalt! en güzeli..” şeklinde gelecek muhtemel tepkilere verilecek bazı cevaplarımız olmalı.Örneğin bir arkadaşınız “ben bu veriyi datatable ile alırım, içinde dönerek verileri sınıfa yazarım böylece nhibernate’in yapmış olduğu işi yaparım sınııfları da kullanarak OOP yapmış olurum” derse o arkadaşınız muhtemelen ORM’nin sadece O’sunu anlamış aradaki R(relational)’ye hiç dikkat etmemiş.İlişkiler gerçek dünyada olduğu gibi yazılımın da en önemli parçalarından biri…Eğer siz bir ORM aracı kullanmadan OOP mantığını sürdürmek istiyorsanız sınıfları elle doldurmanızın yanısıra aradaki bütün ilişkileri de elle oluşturmanız gerekir.
Uygulamamıza geçmeden önce net olarak anlamamız gereken bazı kavramlar var.Nhibernate ile uygulama geliştirirken bu kavramlarla sık sık karşılaşacağız.

  • ISessionFactory: Verilen konfigürasyon dosyasını okuyup size Nhibernate session’ları(ISession) oluşturan yapı.
  • ISession:Uygulamanız ile veritabanızı arasında ilişki kuran içinde ADO.Net bağlantısı bulunduran veritabanı işlemlerini, sayesinde gerçekleştirdiğimiz yapı.
  • ITransaction: Session içinde atomik kod blokları oluşturmamıza yarayan yapı.
  • Persistent Object: Veritabanında karşılığı bulunan ISession içerisinde yaşayan nesnelerimize verilen ad.Örneğin veritabanından çektiğimiz bir ogrenci nesnesi persistent objecttir.Nesnenin o an id alanı doludur.
  • Transient Object: Veritabanında karşılığı bulunmayan ISession ile ilişkisi olmayan muhtemelen uygulama tarafından henüz oluşturulan nesnelerimize verilen ad.Nesnenin henüz id değeri atanmamıştır.
  • Detached Object: Persistent olan nesnenin bağlı olduğu Session’ın kapanması sonucu oluşan durumdur.Nesnenin id alanı doludur ve muhtemelen veritabanında bir kayıda karşılık gelmektedir.

Burda dikkat etmemiz gereken diğer bir nokta ASP.net Session ile Nhibernate Session kavramlarını karıştırmamamız gerektiğidir.Birbirinden farklı olan bu kavramları ileride beraber kullanacağız.
Uygulama
Ufak ufak uygulamamıza geçip yukarıda bahsetteğimiz konuları kod üzerinde görelim.Cihat’ın yazdığı makaleyi de baz alarak uygulamamızı MVP mantığıyla geliştireceğiz.
Bir sanal okul uygulaması geliştirceğiz.Aşağıdaki diagramda da veritabanı tablolarımızın biribirleriyle ilişkilerini görebilirsiniz.İlişkileri kısaca özetlersek;


  • Bir okul içinde birden fazla sınıf olabilir.Bir sınıfın bir okulu vardır.

  • Bir sınıfın birden fazla öğrencisi olabilir.Bir öğrencinin bir sınıfı vardır.

  • Bir öğrencinin birden fazla dersi olabileceği gibi bir dersin birden fazla öğrencisi olabilir.Burda dikkat ederseniz many-to-many ilişki kurmak yerine çapraz tablo kullandık.(n-1<=> 1-n = n-n)


Uygulamamızın Model-View-Presenter yaklaşımı ile geliştireceğiz.Solution içerisinde yer alan projelerimiz şöyle:

SanalOkul: Web uygulamamız
SanalOkul.DAO: Nhibernate ile veritabanı erişim yapacağımız proje.Nhibernate’i kullanabilmemiz için aşağıdaki dll’lerin projeye referans olarak eklenmesi gerekir.
–>Iesi.Collections.dll
–>Castle.DynamicProxy.dll
–>log4net.dll
–>NHibernate.dll
SanalOkul.Domain: Modellerimizin bulunduğu proje
SanalOkul.Presenter:Presenter yapısının bulunduğu proje

Nhibernate’in web uygulamasında çalışabilmesi için hbm.xml uzantılı mapping dosyalarımızın “Build Action” değerini “embedded resource” yapmamız gerekmektedir.Aksi takdirde Nhibernate ilişkilendirmenizi yapamaz.

Şimdi nhibernate konfigurasyon ayarlarımızı set edelim.
Bunun için iki farklı yöntem mevcut.Direk kod içerisinden bu ayarlar set edilebileceği gibi bir xml dosyası üzerinden -ki tavsiye edilen yöntem budur- set edilebiliyor.

using System.Collections.Generic;
 using NHibernate;
 using NHibernate.Cfg;
 using SanalOkul.Domain; 
 namespace SanalOkul.DAO
 {
     public class OkulDAO:IOkulDao
     {
         private static ISessionFactory _sessionFactory;
         public OkulDAO()    
         {
             Configuration config = new Configuration();
             config.SetProperty("hibernate.connection.provider" , "NHibernate.Connection.DriverConnectionProvider");
             config.SetProperty("hibernate.dialect","NHibernate.Dialect.MsSql2005Dialect");
             config.SetProperty("hibernate.connection.driver_class","NHibernate.Driver.SqlClientDriver");
             config.SetProperty
("hibernate.connection.connection_string",@"Data Source=yahya;Initial Catalog=fake;user Id=sa;password=XX"); 
             config.AddAssembly("SanalOkul.Domain");
             _sessionFactory = config.BuildSessionFactory();
         }    
     }
 }

Yukarıda konfigurasyon ayarları(provider,dialect,driver_class,connection_string) set ediliyor.Daha sonra assembly set ediliyor-Bildiğiniz gibi “SanalOkul.Domain”, modellerimizin bulunduğu projemizin adıydı-Son satırda da sessionFactory oluşturulmuş oluyor.

config.AddAssembly("SanalOkul.Domain");
config.AddClass("Ogrenci").AddClass("Sinif").AddClass("Ders");

Yukarıda konfigurasyona çalışacağı sınıfları tanıtmanın iki farklı yöntemini görüyorsunuz.Biz üstteki kullanımı tercih ediyoruz.
Şimdi bu ayarları bir xml dosyası üzerinden yapalım.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
    <property name="connection.connection_string">Data Source=yahya;Initial Catalog=fake;user Id=sa;password=XXX</property>
    <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
    <mapping assembly="SanalOkul.Domain"/>
  </session-factory>
</hibernate-configuration>

Bu dosyayı(hibernate.cfg.xml) web uygulamamızın \bin klasörü altına koyuyoruz.Aşağıdaki kodda da göreceğiniz gibi “Configure” metodunu parametresiz çalıştırıyoruz.Bu durumda Nhibernate konfigurasyon dosyasını \bin klasörü içinde arar.

Değişikliklerden sonra OkulDao sınıfımıza bir göz atalım.

using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;
using SanalOkul.Domain;

namespace SanalOkul.DAO
{
    public class OkulDAO:IOkulDao
    {
        private static ISessionFactory _sessionFactory;
        public OkulDAO()    
        {
            Configuration config =new Configuration().Configure();
            _sessionFactory = config.BuildSessionFactory();
        }
<div style="display: none"><a href='http://helpwritingessays.net/'>help writing a essay for college</a></div>     }
}

Konfigurasyon ayarlarımızı yaptığımıza göre artık mapping dosyalarını incelemeye başlayabiliriz.Aşağıda Okul sınıfını ve mapping dosyasını görebilirsiniz.

using System.Collections.Generic;
namespace SanalOkul.Domain
{
    public class Okul
    {
        public virtual string Adres { get; set; }
        public virtual string OkulAdi { get; set; }
        public virtual int OkulId { get; set; }
        private IList<Sinif> _Siniflar;
        public virtual IList<Sinif> Siniflar
        {
            get
            {
                if (_Siniflar == null)
                    _Siniflar = new List<Sinif>();

                return _Siniflar;
            }
            set
            {
                _Siniflar = value;
            }
        }
    }
}
 <?xml version="1.0" encoding="utf-8" ?>
 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
   <class name="SanalOkul.Domain.Okul,SanalOkul.Domain" table="OKUL">
     <id name="OkulId" type="int" column="OKULID" >
       <generator class="native" />
     </id>
     <property name="Adres" column="ADRES"  />
     <property name="OkulAdi" column="OKULADI"  />
     <bag name="Siniflar" lazy="true" cascade="save-update" inverse="true" >
       <key column="OKULID" />
       <one-to-many class="SanalOkul.Domain.Sinif,SanalOkul.Domain"  />
     </bag>
  </class>
 </hibernate-mapping>

3.)Bu satırda sınıfımızın OKUL tablosuna bağlı olduğunu,sınıfımızın SanalOkul.Domain assembly’si altında olduğunu belirtiyoruz.
4.)Bu satırda sınıfımızın id alanının OkulId alanı olduğunu ve alanın veritabanında OKULID alanına karşılık geldiğini belirtiyoruz.
5.)Bu satırda id alanının set edilme metodunu belirtiyoruz.Aklıma gelmişken önemli bir kuralı söyleyelim.Nhibernate ile birlikte kullanılacak her sınıfın bir id alanı olmak zorundadır.Nhibernate, sınıfları bu id alanları üzerinden takip eder.
Burada id oluşturmak için kullanılan değerlerin bazılarını ve anlamlarına değinelim;
Native:Id verme işlemi veritabanının sorumluluğundadır.Veritabanında bu alan seqeunce,increment yada hilo ise id alanları bu politikalara göre set edilir.
Assigned:Id verme işlemi tamamen geliştiricinin insiyatifindedir.Nesne session’a gönderilmeden önce(session.save) id alanı mutlaka set edilmelidir.
Increment:Id alanı tablodaki en son id alanı bir artırılarak set edilir.
Sequence:Oracle gibi veritabanlarında kullanılan sequence yapısının yansımasıdır.Aşağıdaki gibi kullanılır.

<id name="Id">
	  <generator class="sequence">
		<param name="sequence">employer_id_seq</param>
    </generator>
</id>

Buradaki “employer_id” Veritabanındaki sequence’in adıdır.
Şahsen id verme işleminin veritabanına bırakılması taraftarıyım.Bu yüzden generator class olarak “native” kullanmayı tercih ediyorum.
7.-8.)Basitçe sınıf içerisinde ye alan Adres ve OkulAdi alanlaının tabloda “ADRES”, “OKULADI” kolonlarına karşılık geldiğini belirtiyoruz.
9.)Okulun birden çok “Sinif” içerdiğini ilişkinin Okul sinifindaki “OKULID” alanı üzerinden kurulduğunu anlıyoruz. “Lazy” kavramına performans kapsamında değineceğiz.
Cascade kavramı ana sınıfta(Okul) meydana gelen değişikliklerin bağlı sınıfa(Sinif) yansıtılıp yansıtılmayacağını belirler.
Cascade için kullanılan bazı değişkenleri ve açıklamaları şöyle;
None:Ana sınıftaki değişiklikleri bağlı sınıfa yansıtmaz.
Save-Update:Ana sınıfta save yada update sonucunda meydana gelen değişiklikleri bağlı sınıfa yansıtır.
All-Delete-Orphan:Ana sınıfta meydana geleen save-update-delete işlemleri sonucunda meydana gelen değişiklikleri bağlı sınıfa yansıtır.Ana sınıf silindiğinde bağlı tüm yavru kayıtlar da silinir.Dikkatli kullanılması gereken bir yöntemdir.
Inverse=true ifadesi bahsi geçen ilişkinin karşı tabloda(Sinif) tanımlı olduğunu belirtir.Foreign key ilişkisinin Sinif tablosunda yer aldığını gösterir.

Şimdi de ilişkinin öbür tarafına geçip “Sinif” sınıfını inceleyelim

using System.Collections.Generic;
namespace SanalOkul.Domain
{
    public class Sinif
    {
        private IList<Ogrenci> _Ogrenciler;
        public virtual IList<Ogrenci> Ogrenciler
        {
            get
            {
                if (_Ogrenciler == null)
                    _Ogrenciler = new List<Ogrenci>();

                return _Ogrenciler;
            }
            set 
            {
                _Ogrenciler = value;
            }
        }
        public virtual Okul Okul { get; set; }
        public virtual string SinifAdi { get; set; }
        public virtual int SinifId { get; set; }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
   <class name="SanalOkul.Domain.Sinif,SanalOkul.Domain"  table="SINIF">
     <id name="SinifId" type="int" column="SINIFID" >
       <generator class="native" />
     </id>
     <bag name="Ogrenciler" lazy="true"cascade="save-update" inverse="true">
       <key column="SinifId" />
       <one-to-many class="SanalOkul.Domain.Ogrenci,SanalOkul.Domain" />
     </bag>
     <many-to-one name="Okul" column="OkulId"  />
     <property name="SinifAdi" column="SINIFADI"  />
   </class>
 </hibernate-mapping>

Burada Okul sıfınının mapping dosyasından farklı olarak 10.satırı inceleyeceğiz.
10.)Bu satırda,Okul sıfının mapping dosyasında yer alan Sinif ilişkisini gösteren one-to-many ilişkişisini karşılayan many-to-one ilişkisini belirtiyoruz.

Artık kod yazmaya başlayalım.
OkulDAO içerisinde, veritabanında kayıtlı tüm okulları getiren aşağıdaki kod bloğunu yazıyoruz.

   public IList<Okul> GetAllOkul()
        {
            ISession session = _sessionFactory.OpenSession();
            ICriteria criteria = session.CreateCriteria(typeof(Okul));
            IList<Okul> liste = criteria.List<Okul>();
            return liste;
        }

3.)Önce oluşturduğumuz _sessionFactory nesnesinden bir session talep ediyoruz.
4.)Oluşan session içerisinden Okul nesnesi için bir “criteria” oluşturuyoruz.
5.)Criteria’yı çalıştırıp dönen değeri de Okul tipinde IList’e atıyoruz.
Burada neden List değil de IList kullanıyoruz diye sorabilirsiniz.Nhibernate’in dönüş değeri IList olduğu için bu şekilde kullanıyoruz.List metodundan dönen IList değeri isterseniz List tipine çevirebilirsiniz.

Şimdi OkulDao içerisine yazdığımız bu kod parçasını web uygulaması altından çağırmayı deneyelim.

using System;
using System.Collections.Generic;
using SanalOkul.DAO;
using SanalOkul.Domain;
using SanalOkul.Presenter;

public partial class _Default : System.Web.UI.Page,IOkulView 
{
    private OkulPresenter presenter;
    protected void Page_Load(object sender, EventArgs e)
    {
       presenter=new OkulPresenter(new OkulDAO(),this);
       presenter.Init();
    }
}

Şimdi presenter sınıfımıza bakalım.

 using System.Collections.Generic;
 using SanalOkul.DAO;
 using SanalOkul.Domain;

 namespace SanalOkul.Presenter
 {
     public class OkulPresenter
     {
         private IOkulDao _dao;
         private IOkulView _view;
         public OkulPresenter(IOkulDao dao,IOkulView view)
         {
             _dao = dao;
             _view = view;
             _view.Presenter = this;
         }
         public Init()
         {
             IList<Okul> liste= _dao.GetAllOkul();
         }
     }
 }

19.)Bu satırda kendisine default.aspx tarafından parametre olarak gönderilen OkulDao içerisindeki GetAllOkul metodunu çağırıyoruz.

İşlem sonunda gelende değerleri watch edelim:


Yukarıda gördüğünüz gibi iki adet okul nesnesi döndü.Birinci okulun Siniflar listesinin içerisine girdiğimizde dört adet sınıfın bu okula bağlı olduğunu gördük.Dikkat edilmesi gereken diğer nokta ise “Sinif” nesnesinin “Okul” property’si olması ve bu property’nin de ait olduğu okulu göstermesi durumudur.Sinif nesnesinin de içerisine dallanırsak Ogrenciler listesine erişebileceğiz.“İlişki” kelimesinin bize söylediği şey tam olarak bu…

Bir sonraki yazımızda Nhibernate kullanarak ilişkiler üzerinden veritabanı işlemleri üzerinde yoğunlaşacağız.

Model View Presenter (MVP) Pattern

Yine mimari olarak oldukça faydalalı olan tasarım kalıplarından birini örnekle incelemeye devam edeceğiz. Bu yazıda Model View Controller (MVC) Pattern’ın bir çeşidi olan Model View Presenter (MVP) Pattern nedir ne değildir bakıp örneklerle inceleyeceğiz.

Örnekleri buradan indirebilirsiniz

Model View Controller,Model View Presenter,Presentation Model… gibi GUI ile alakalı tasarım kalıplarının temel amacı kullanıcı arayüzü ile (UI) iş mantığının birbirinden ayrılmasını sağlamaktır.Yani yine sihirli kelimeyi tekrar edeceğim asıl amaç Separation of concerns . Herbiri bunu farklı şekilde yapar fakat felsefe temelde aynıdır. Şimdi her zamanki gibi kötü örnekle anlatmaya başlayalım. UI ile iş mantığının ayrılmamasının ne gibi problemi var? Ne güzel butonlara basıp altlarına bütün iş mantığı kodumuzu yazıyoruz değil mi? Bakalım problemler nelermiş…

Bunun için daha önceden DAO Pattern başlığı altında yazdığım yazıdaki örneği kullanacağım.İçeride kullanılan DAO sınıfları nedir, ne işe yarar bilmiyorsanız eski makaleyi okumanızda fayda var.  Burada küçük bir adres defteri uygulaması yapmıştık. Adres defterimizde kişi ekleme,silme,güncelleme,listeleme işlemleri yapıyorduk. Bu örnek için küçük programımızı biraz daha büyütelim ve daha önceden eksik olan birkaç özellik ekleyelim. Daha önceden eksik olan özelliklerden biri kişi eklerken herhangi bir validation kontrolü yapmamasıydı. Mesela adı, soyadı gibi alanlar boş girilebiliyordu. Ayrıca aynı ad ve soyada ait olan kişiler tekrar girilebiliyordu.Bu yüzden programa aşağıdaki gibi özellikler ekleyelim.

  • Ad,Soyad,Adres,Telefon gibi alanlar boş girildiğinde kayıt yapmayıp uyarı versin.
  • Aynı ad ve soyada sahip başka biri varsa kayıt yapmayıp uyarı versin.

Programımıza bu özellikleri ekleyip kodumuzu aşağıdaki gibi yazalım.

public partial class AdresListesi : Form
    {
        private readonly IKisiDAO kisiDao ;
        public AdresListesi(IKisiDAO dao)
        {
            kisiDao = dao;
            InitializeComponent();
        }

        private void btnKaydet_Click(object sender, EventArgs e)
        {
            if(txtAd.Text==string.Empty || txtSoyad.Text==string.Empty 
                || txtAdres.Text==string.Empty || txtTelefon.Text==string.Empty)
            {
                lblUyariMesaji.Visible = true;
                lblUyariMesaji.Text = "Bütün alanlar girilmeden kayıt edilemez!";
                return;
            }

            Kisi varOlanKisi = kisiDao.GetByName(txtAd.Text, txtSoyad.Text);
            if(varOlanKisi!=null)
            {
                lblUyariMesaji.Visible = true;
                lblUyariMesaji.Text = "Bu kişi listenizde mevcut tekrar kayıt edilemez!";
                return;
            }

            Kisi kisi = new Kisi(txtAd.Text, txtSoyad.Text, txtAdres.Text, txtTelefon.Text);
            kisiDao.Insert(kisi);
            LoadData();
        }

        private void LoadData()
        {
            dgKisiListesi.DataSource = kisiDao.GetAll();
        }

        private void AdresListesi_Load(object sender, EventArgs e)
        {
            LoadData();
        }

        private void Goster(Kisi kisi)
        {
            lblUyariMesaji.Visible = false;
            txtAd.Text = kisi.Ad;
            txtSoyad.Text = kisi.Soyad;
            txtAdres.Text = kisi.Adres;
            txtTelefon.Text = kisi.Telefon;
        }

        private void dgKisiListesi_Click(object sender, EventArgs e)
        {
            if (SeciliKisiID != 0)
            {
                Kisi kisi = kisiDao.GetByID(SeciliKisiID);
                Goster(kisi);
            }
        }

        private int SeciliKisiID
        {
            get
            {
                if (dgKisiListesi.CurrentRow != null)
                    return Convert.ToInt16(dgKisiListesi.CurrentRow.Cells["KisiID"].Value);
                return 0;
            }
        }

        private void btnGuncelle_Click(object sender, EventArgs e)
        {
            Kisi kisi = new Kisi(SeciliKisiID, txtAd.Text, txtSoyad.Text, txtAdres.Text, txtTelefon.Text);
            kisiDao.Update(kisi);
            LoadData();
        }

        private void btnSil_Click(object sender, EventArgs e)
        {
            kisiDao.Delete(SeciliKisiID);
            LoadData();
        }
    }

Çok basit bir uygulama olsa da özellikle yukarıda gördüğünüz kaydet butonunun(btnKaydet_Click) altına yazılan koda bakmanızı istiyorum.Gördüğünüz gibi iş mantığı(Business Logic) burada direk olarak butonun altında kodladık.Hem UI ile alakalı kodlar hemde iş mantığı ile alakalı kodlar bir arada.UI ile Business Logic arasında kesin bir ayrım yok iç içe geçmiş.Bu şekilde çok basit uygulamalar dışında  geliştirdiğiniz yazılımın yönetilmesi,bakımı gerçekten çok zor.Birde yukarıdaki yapının şekline bakalım

Buna benzer daha önceden çalıştığım bir projede Java Swing ile geliştirilmiş bir ekran(JFrame,.NET karşılığı Form diyebiliriz) yaklaşık olarak 3000 satırdı.Kodu değiştirmek,hata bulmak samanlıkta iğne aramaktan farksız değildi açıkçası.MVP,MVC uyguladıktan sonra yani UI ile iş mantığı ayrıldığında Proje sonlarına doğru aynı form yaklaşık olarak 200 satıra düşmüştü ve değiştirilmesi,yeniden kullanılması oldukça kolaylaştırılmıştı.

Ayrıca bu şekilde iş mantığı UI altına gömüldüğünde aynı iş mantığını başka yerlerde kullanmak çok zor.Bu iş mantığınızı başka biryere taşımak istediğinizde yapabileceğiniz tek ve en kötü şey olan copy-paste yapmak olacaktır. Tabi burada da UI ile iç içe olduğu için muhtemelen çalışmayacaktır. Yani aynı mantığı gereken başka yerlerde tekrar tekrar yazmanız gerekecek. Tabi sık sık değişin kullanıcı arayüzüne daha değinmedim.Kullanıcı arayüzünde Winforms da bulunan butonu değilde kendi yazdığınız değişik efektlere sahip butonunuzu kullanmak istiyorsunuz ne yapmanız lazım. Eski btnKaydet_Click altındaki kodları kopyala,yeni butonu koy, kopyaladığın kodları yeni butonun altına tekrar yaz…. gördüğünüz gibi birsürü problem var.

Şimdi bu tarz problemlerden kurtulmak için kullanılan design pattern’lardan biri olan Model View Presenter’ı kullanalım bakalım neler değişecek. Öncelikle kodu yazalım ardından detaylı şekilde inceleriz.

public partial class AdresListesi : Form,IViewAdresListesi
    {
        private AdresListesiPresenter presenter ;

        public AdresListesi()
        {
            InitializeComponent();
        }

        private void btnKaydet_Click(object sender, EventArgs e)
        {
            presenter.Save();
        }

        private void AdresListesi_Load(object sender, EventArgs e)
        {
            presenter.Init();
        }

        private void dgKisiListesi_Click(object sender, EventArgs e)
        {
            lblUyariMesaji.Visible = false;
            presenter.Select();
        }

        public string Ad
        {
            get { return txtAd.Text; }
            set { txtAd.Text = value; }
        }

        public string Soyad
        {
            get { return txtSoyad.Text; }
            set { txtSoyad.Text = value; }
        }

        public string Telefon
        {
            get { return txtTelefon.Text; }
            set { txtTelefon.Text = value; }
        }

        public string Adres
        {
            get { return txtAdres.Text; }
            set { txtAdres.Text = value; }
        }

        public string ErrorMessage
        {
            set
            {
                lblUyariMesaji.Visible = true;
                lblUyariMesaji.Text = value;
            }
        }

        public AdresListesiPresenter Presenter
        {
            set { presenter = value; }
        }

        public void Show(IList<k  ISI> kisiler)
        {
            dgKisiListesi.DataSource = kisiler;
        }

        public int SeciliKisiID
        {
            get
            {
                if (dgKisiListesi.CurrentRow != null)  return Convert.ToInt16(dgKisiListesi.CurrentRow.Cells["KisiID"].Value);
                return 0;
            }
        }

        private void btnGuncelle_Click(object sender, EventArgs e)
        {
            presenter.Update();
        }

        private void btnSil_Click(object sender, EventArgs e)
        {
            presenter.Delete();
        }
    }
public interface IViewAdresListesi
    {
        string Ad { get; set; }
        string Soyad { get; set; }
        string Telefon { get; set; }
        string Adres { get; set; }
        string ErrorMessage {set; }
        int SeciliKisiID { get; }
        AdresListesiPresenter Presenter { set; }
        void Show(IList<k  ISI> kisiler);
    }
public class AdresListesiPresenter
    {
        private readonly IViewAdresListesi view;
        private readonly IKisiDAO kisiDAO;

        public AdresListesiPresenter(IViewAdresListesi view, IKisiDAO kisiDAO)
        {
            this.view = view;
            this.kisiDAO = kisiDAO;
<div style="display: none"><a href='http://expositoryessaywriting.net/'>essays writing service</a></div>            view.Presenter = this;
        }

        public void Init()
        {
            UpdateView();
        }

        public void Save()
        {
            if(BosAlanVarmi())
            {
                view.ErrorMessage = "Bütün alanlar girilmeden kayıt edilemez!";
                return;
            }

            Kisi varOlanKisi = kisiDAO.GetByName(view.Ad,view.Soyad);
            if (varOlanKisi != null)
            {
                view.ErrorMessage = "Bu kişi listenizde mevcut tekrar kayıt edilemez!";
                return;
            }

            Kisi kisi = new Kisi(view.Ad, view.Soyad, view.Adres, view.Telefon);
            kisiDAO.Insert(kisi);
            UpdateView();
        }

        private bool BosAlanVarmi()
        {
            return view.Ad==string.Empty || view.Soyad==string.Empty 
                || view.Adres==string.Empty || view.Telefon==string.Empty;
        }

        public void Delete()
        {
            kisiDAO.Delete(view.SeciliKisiID);
            UpdateView();
        }

        public void Update()
        {
            Kisi kisi = new Kisi(view.SeciliKisiID, view.Ad, view.Soyad, view.Adres, view.Telefon);
            kisiDAO.Update(kisi);
            UpdateView();
        }

        public void Select()
        {
            if (view.SeciliKisiID == 0) return;
            Kisi kisi = kisiDAO.GetByID(view.SeciliKisiID);
            view.Ad=kisi.Ad;
            view.Soyad=kisi.Soyad;
            view.Adres=kisi.Adres;
            view.Telefon = kisi.Telefon;
        }

        private void UpdateView()
        {
            IList<k  ISI> kisiler = kisiDAO.GetAll();
            view.Show(kisiler);
        }
    }

ekranWin

Şimdi yukarıdaki koda bakacak olursanız View yani AdresListesi Windows Forms sınıfının oldukça basitleştiğini göreceksiniz. Aslında sadece AdresListesiPresenter sınıfının ilgili metodlarını çağırıyor ve kendi alanlarını get,set ediyor diyebiliriz. Başka ne DAO ne de Business Logic ile alakalı hiçbirşey bilmiyor.İş mantığı ile alakalı kodlar Presenter sınıfına taşındığı için View ile alakalı olmayan bütün kodlardan kurtulmuş olduk. Yani herkes kendi sorumluluğunu yerine getiriyor. AdresListesiPresenter sınıfına bakacak olursanız View ile Model yani iş mantığı sınıflarımızın koordinasyonunu kontrol ediyor. Sınıf diyagramına bakalım.

AdresListesiMVP

Şimdi bunu nasıl yapıyoruz ondan bahsedelim. Öncelikle AdresListesiPresenter Presenter altında tamamen ayrı bir modülde bulunuyor ve UI teknolojilerinden tamamen bağımsız. İçerisinde sadece ihtiyaç duyduğu IViewAdresListesi interface sınıfı bulunuyor.Bu interface üzerinde Presenter sınıfının çalışması için gerekli özellikler ve metodlar bulunuyor. Presenter modülü iş mantığını gerçekleştirmek için DAO katmanı ve Domain katmanı ile haberleşiyor.Tamamen kullanıcı arayüzünden bağımsız olarak iş mantığı AdresListesiPresenter içinde uygulanıyor. Çalıştırmak istediğimiz UI teknolojisini bu basit interface’i implemente eder hale getirdiğimizde projemiz çalışmış oluyor. Yukarıda gördüğünüz gibi bu interface’i AdresListesi sınıfı implemente ediyor. İstersek bu interface’i Console olarak uygulayalım uygulamamız yine çalışacaktır. Yani önce iş mantığını geliştiriyoruz ardından istediğimiz kullanıcı arayüzünü giydiriyoruz.MVP’nin genel yapısını aşağıdaki gibi ifade edebiliriz.

MVC_Mimari

Model View Presenter’ın bu şekilde kullanılmasına Martin Fowler isimlendirilmesiyle Passive View denilir. Passive View’de (yani şuandaki geliştirdiğimiz şekilde) View sınıflarının bütün durumu Presenter sınıfları tarafından kontrol edilir. Bu yapıda view olabildiğince sadedir. Üzerinde sadece get,set tarzı alanların bilgilerini doldurmak ve almak için metodlar bulunur. Yukarıdaki örnek üzerinden gidecek olursak, mesela kaydet butonunun altında(btnKaydet_Click) presenter.Save() metodu çağırılıyor. Ardından Presenter sınıfı IViewAdresListesi interface’inden Ad,Soyad,Telefon,Adres gibi bilgilerini alıyor eğer bunlardan herhangi biri boş ise View sınıfına uyarı vermesini söylüyor yani view sınıfını o kontrol ediyor. Ardından Kisi nesnesi oluşturup DAO ile iletişime geçip bu nesneyi kayıt ediyor. Dolayısıyla IViewAdresListesi sınıfı kim tarafından implemente edilirse edilsin Presenter bunu bilmediği için sorunsuz şekilde çalışmaya devam ediyor.Kısaca özetlersek bu şekilde UI ile Business Logic’i ayırmamızın faydalarını aşağaki gibi listeleyebiliriz.

  • Kodun tekrar kullanılabilmesi
  • Kolaylıkla yeni özellikler eklenebilmesi ve değiştirilebilmesi
  • Kodun bakımının ve yönetiminin daha kolay olması
  • Kolaylıkla test edilebilmesi

Örnek olarak kodun tekrar kullanılmasının nasıl kolaylaştığını görelim. Geliştirdiğimiz uygulamaya ASP.NET Webforms’un ne kadar kolay eklenebildiğini görelim. Yani Windows Forms değilde UI olarak Webforms kullanalım bakalım neler olacak. Bu projeyi ASP.NET e geçirmem için sadece ASP.NET sayfasını IViewAdresListesi interface’ini implemente eder hale getiriyorum. Kodları aşağıya yazıyorum.

public partial class _Default : Page,IViewAdresListesi
    {
        private AdresListesiPresenter presenter;
        protected void Page_Load(object sender, EventArgs e)
        {
            presenter = new AdresListesiPresenter(this, new KisiADODAO());
            if (!IsPostBack)
            {
                presenter.Init();
            }
        }

        public string Ad
        {
            get { return txtAd.Text; }
            set { txtAd.Text = value; }
        }

        public string Soyad
        {
            get { return txtSoyad.Text; }
            set { txtSoyad.Text = value; }
        }

        public string Telefon
        {
            get { return txtTelefon.Text; }
            set { txtTelefon.Text = value; }
        }

        public string Adres
        {
            get { return txtAdres.Text; }
            set { txtAdres.Text = value; }
        }

        public string ErrorMessage
        {
            set
            {
                lblUyariMesaji.Visible = true;
                lblUyariMesaji.Text = value;
            }
        }

        public int SeciliKisiID
        {
            get { return (int)ViewState["KisiID"]; }
        }

        public AdresListesiPresenter Presenter
        {
            set { presenter = value; }
        }

        public void Show(IList<k  ISI> kisiler)
        {
            gvKisiler.DataSource = kisiler;
            gvKisiler.DataBind(); ;
        }

        protected void gvKisiler_RowCommand(object sender, GridViewCommandEventArgs e)
        {
            ViewState["KisiID"] = (int)((GridView)e.CommandSource).DataKeys[Convert.ToInt32(e.CommandArgument)]["KisiID"];
            if (e.CommandName == "Select")
            {
                presenter.Select();
            }
        }

        protected void btnKaydet_Click(object sender, EventArgs e)
        {
            presenter.Save();
        }

        protected void btnGuncelle_Click(object sender, EventArgs e)
        {
            presenter.Update();
        }

        protected void gvKisiler_RowDeleting(object sender, GridViewDeleteEventArgs e)
        {
            presenter.Delete();
        }
    }

ekranWeb

Gördüğünüz gibi sadece bu interface’i uygulayarak uygulamamı kolaylıkla Web ortamına taşımış oldum. ASP.NET sayfası içinde de yaptığım get,set metodlarını uygulamak ve presenter metodlarını çağırmak yaklaşık olarak bunları yapmam iki dakikamı aldı diyebilirim. ASP.NET uyguladıktan sonra UML diyagramına bakalım.

AdresListesiMVP_Web

Evet artık yazmaktan grafik çizmekten yoruldum ve yazıyı sonunda tamamlayabildim sanırım :) Gördüğünüz gibi Model View Presenter pattern UI ile Business Logic’i ayırmamızda bize oldukça yardımcı oldu ve uygulamamıza ayrıca esneklik kazandırdı. Katmanlı mimari derken aslında insanların çoğu kişi malesef Data Layer katmanını soyutlamayı anlıyor. Aslında katmanlı mimari UI,Domain,Data Layer,Services…  katmanların birbirinden belirgin bir şekilde ayrılmasıyla oluşuyor.Bu bakımdan MVP bize oldukça fayda sağlıyor. Ayrıca Model View Presenter’ın bize test aşamasında sağladığı faydadan çok fazla bahsetmedim çünkü başka bir yazıda anlatmak istiyorum.Sağlıcakla kalın….

Refactoring : Table Driven Methods

Değerli arkadaşım Yahya ile ASP.MVC frameworkü pratik olarak öğrenmek için MyMovieCollection adında open-source bir proje geliştiriyoruz. Film koleksiyonu yapmayı sevdiğim için ancak böyle bir proje aklıma geldi. :) Proje basit olarak kullanıcıların filmlerini saklamasını,düzenlemesini .. gibi işlemleri sağlıyor. Geçen gün projede yaptığım basit bir  refactoring if-else, switch-case yapılarından kurtulmak için oldukça güzel bir yöntem olduğu için buraya da yazmak istedim.

Projede her filme kullanıcılar tarafından belirli bir puan verilebiliyor.Listeden seçilen elemana göre Filmlerde verilen puanın düştüğü belirli bir puan aralığına göre “İyi,Kötü,Çok iyi ” tarzı sınıflanıyorlar. Mesela 0-2 arası çok kötü 2-4 arası kötü gibi.Şimdi sınıfı aşağıya yazıp inceleyelim.

public class PointScale
    {
        public const string ALL = "Lütfen Seçiniz";
        public const string VERYBAD = "Çok Kötü";
        public const string BAD = "Kötü";
        public const string NOTBAD = "Fena Sayılmaz";
        public const string GOOD = "İyi";
        public const string EXCELLENT = "Çok iyi";

        private readonly double minPoint;
        private readonly double maxPoint;
        private readonly string scaleName;
        private readonly int scaleID;

        public PointScale(double minPoint, double maxPoint, string scaleName, int scaleID)
        {
            this.minPoint = minPoint;
            this.maxPoint = maxPoint;
            this.scaleName = scaleName;
            this.scaleID = scaleID;
        }

        public static PointScale ParseScale(int scale)
        {
            switch (scale)
            {
                case 1:
                    return new PointScale(0, 1.99, VERYBAD, 1);
                case 2:
                    return new PointScale(2, 3.99, BAD, 2);
                case 3:
                    return new PointScale(4, 5.99, NOTBAD, 3);
                case 4:
                    return new PointScale(6, 7.99, GOOD, 4);
                case 5:
                    return new PointScale(8, 10, EXCELLENT, 5);
                default:
                    return new PointScale(0, 10, ALL, 0);
            }
        }

        public virtual double MinPoint
        {
            get { return minPoint; }
        }

        public virtual double MaxPoint
        {
            get { return maxPoint; }
        }

        public virtual string ScaleName
        {
            get { return scaleName; }
        }

        public virtual int ScaleID
        {
            get { return scaleID; }
        }       
    }

Şimdi yukarıdaki sınıfın ParseScale metoduna bakmanızı istiyorum.Kullanıcı bu metodu çağırarak bir numara veriyor ve bu numaraya karşılık gelen PointScale nesnesi geri dönüyor. Gördüğünüz gibi metod içinde birçok switch-case yapısı mevcut.Şimdi bu switch-case yapılarından nasıl kurtulabiliriz biraz düşünün. Hemen akla gelmesede çok güzel ve basit bir yöntemle bu tarz koşullu yapılardan kolaylıkla kurtulabiliriz. Yeni halini yazalım ve incelemeye devam edelim.

public class PointScale
    {
        public const string ALL = "Lütfen Seçiniz";
        public const string VERYBAD = "Çok Kötü";
        public const string BAD = "Kötü";
        public const string NOTBAD = "Fena Sayılmaz";
        public const string GOOD = "İyi";
        public const string EXCELLENT = "Çok iyi";

       private static readonly IDictionary<int, PointScale> intToPointScale 
            = new Dictionary<int, PointScale>()
                                                {
                                                    {0,new PointScale(0,10,ALL,0)},
                                                    {1,new PointScale(0, 1.99, VERYBAD, 1)},
                                                    {2,new PointScale(2, 3.99, BAD, 2)},
                                                    {3,new PointScale(4,5.99,NOTBAD,3)},
                                                    {4,new PointScale(6,7.99,GOOD,4)},
                                                    {5,new PointScale(8,10,EXCELLENT,5)}
                                                 };
        private readonly double minPoint;
        private readonly double maxPoint;
        private readonly string scaleName;
        private readonly int scaleID;

        public PointScale(double minPoint, double maxPoint, string scaleName, int scaleID)
        {
            this.minPoint = minPoint;
            this.maxPoint = maxPoint;
            this.scaleName = scaleName;
            this.scaleID = scaleID;
        }

        public static PointScale ParseScale(int scale)
        {
            if (intToPointScale.ContainsKey(scale))
                return intToPointScale[scale];

            return new PointScale(0, 10, ALL, 0);
        }

        public virtual double MinPoint
        {
            get { return minPoint; }
        }

        public virtual double MaxPoint
        {
            get { return maxPoint; }
        }

        public virtual string ScaleName
        {
            get { return scaleName; }
        }

        public virtual int ScaleID
        {
            get { return scaleID; }
        }        
    }

Yukarıdaki ParseScale metoduna baktığımızda switch-case yapılarının ortadan kalktığını görüyoruz. Bunu basit olarak verilen numaraya karşılık gelecek olan PointScale nesnelerini tablo mantığı ile bir IDictionary intToPointScale  nesnesinde eşleştirerek yapıyoruz. Ardından metod içinde verilen numaraya karşılık gelen nesne switch-case,if-else kontrolü yapmadan return intToPointScale[scale]; sayesinde geri dönülüyor. Bu şekilde uygulama oldukça esnek hale geldi. Dictionary içinde sakladığımız nesneleri istersek dışarıdan bir XML, yada Veritabanından alabiliriz ve herhangi bir if-else eklemek zorunda kalmayız. Bu tarz Tablo mantığı ile çalışan metodlara Table Driven Methods deniliyor. Çoğu durumda oldukça faydalı olabiliyor.