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….

19 thoughts on “Model View Presenter (MVP) Pattern

  1. Erol Kabadayı

    Merhaba,

    Müthiş bir emek harcayarak bu makaleleri hazırlıyorsun. Severek ve keyif alarak yazıyorsun. Sadece bu yazı değil neredeyse tamamı aynı derecede güzel.

    Tebrik ederim,

  2. Rahman Duran

    Çok teşekkürler hocam yine harikasınız. Bu alanlarda türkçe kaynak bulmaz zor oluyor. Önemli bir boşluğu dolduruyorsunuz. Aynı performansla devam ederseniz bizi de çok sevindirirsiniz. Sağlıcakla kalın.

  3. murat

    MVP pattern in en önemli özelliği arayüzü test etmede getirdiği kolaylıkdır. Özellikle kodlarını indirip baktım fakat teste dair hiçbirşey yok. özellikle TDD den bahsedipde MVP patterni gösterirken tek satır bile test olmaması düşündürücü. ayrıca fowler amcamın bu patterni ayırıp sen passive sende supervisor sın demesi ve MVP camiasını ikiye bölmesi hakkındada birşeyler yazabilirdin . en azından neden böyle birşeye ihtiyaç duymuşki bu büyük şahıs ve de herkes bunu kabullenmiş. peki eskiden neden kimse bunu farketmemiş? örnek hakkında yine birkaç kelamım var affına sığınarak. ilki yine kolayına geldiğini varsayıyorum :D de merak ettim event ları kullanmak istesem nerde, nasıl kullanıcağım :D .
    DAO daki mesajımdada dediğim gibi sadece emeğini daha yapıcı kılma çabasından başka birşey değil.
    sabırsızlıkla yeni şeyler bekliyoruz ha okuyacağımdan değil eleştirmek için :D. kusura bakma türkiyede eleştirilmeye değer birini bulduğum için artık peşindeyim :)

  4. Pingback: Yazılım Mühendisliği » Data Access Object Pattern (DAO)

  5. M. Cihat Altuntaş Post author

    Test hakkındaki gözlemine yazının sonuna yazdığım “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” sözüm cevap vermiş olur sanırım :) Evet dediklerinde haklısın Supervisor’dan bahsedebilirim fakat konunun gittikçe uzaması pek hoşuma gitmiyor aslında. Bu yüzden bununla alakalı Supervisor,Page Navigation,Session Management gibi diğer konuları başka yazılara saklıyorum. Yazacak konumuz kalsın dimi :)

  6. Pingback: Model View Presenter Uygulamaları « Yazılım Mühendisliği

  7. Pingback: Model View Presenter Uygulamaları « Yazılım Mühendisliği

  8. Barbaros

    Tekrar tekrar teşekkürler,
    Murat beyde bu konularda bilgili görünüyor. Eğer onunda bir blog u yada makale yazdığı bir adres varsa öğrenmek isteriz.

    Elinize ve emeğinize sağlık.

  9. yusuf

    ben MVC yi indirdim ve kurdum. mvp yi de indirdimi peki mvc de installation varken mvp de installation yok. Yani ben asp.net mvc website seçebiliyorsam. mvp asp.net web sitede seçip direkt kodlamalıyım. ama nasıl? yani bir kurulum exe si yok mu?

  10. M. Cihat Altuntaş Post author

    MVC olsun MVP olsun aslında birer uygulama tasarım kalıbı. Bunları ASP.NET, Java ya da herhangi bir dile bağımsız olmaksızın kullanabilirsin. Bazı platformlarda bunların kullanımını kolaylaştırmak için hazır şablonlar yada frameworkler ile bize sunarlar bazılarında ise sunmazlar onları uygulamak ise bize kalır.

    Bence burada bunların ne işi yaptığı bize ne fayda sağladığını anlamak installation ile kullanmaktan daha faydalı o yüzden bunlara değinmedim. MVP’yi kurulum olmadan kendin uygulayabilirsin. Ayrıca Web Client Software Factory adı altında ayrıca hazır framework olarak indirebilirsin.

  11. Tuna Toksoz

    Merhaba Cihat

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

    bu tip bir yapi yerine acaba eventleri kullanip, view tarafinda eventler yaratip presenteri o eventlere baglamak daha iyi olmaz mi ? Sadece bir dusunce.

  12. M. Cihat Altuntaş Post author

    Bahsettiğin yöntem Supervising controller olarak geçiyor sanırım. Ben kişisel olarak Event handling kodlarının Presenter’da olmasından pek hoşlanmıyorum o yüzden Passive View daha çok hoşuma gidiyor. Tabi benimkide sadece bir düşünce dediğin gibi.

  13. Javacikiz

    Çok güzel bir makale olmuş. Çok yararlı bilgiler ve Model Viev Presenter hakkında çok fazla Türkçe kaynak yok. Elinize sağlık.

  14. Pingback: » Data Access Object Pattern (DAO)

  15. Pingback: Kodlama Pratikleri « Koray Kırdinli

  16. Birkan

    Gerçekten anlatım dili çok güzel olmuş. Ben Bilgisayar Mühendisiyim, MVC-MVP konusunu internette araştırırken öyle makalelere denk geldim ki konuyu idrak edememiştim. Nedenini nasılını güzel yedirememişlerdi. Bu Bloğunuzdan 3. faydalanışım. Ne zaman Google da bişi aratsam en tatminkar ve anlışılır cevabı bloğunuzdan edindim.

Comments are closed.