Command Design Pattern

command pattern design pattern

Selam millet!

Command Design Pattern ile tasarım desenleri yolculuğumuza devam ediyoruz.  Neymiş bu Command Pattern, hangi işimizi çözüyormuş gelin daha yakından inceleyelim. Bu tasarım desenimiz de davranışsal tasarım kategorisi altında yer alıyor. Demek ki yine değişen durumlara nesnelerin verdiği tepkiyi ayarlamamız gerekiyor. Eğer merak ettiyseniz, saatlerimizi ayarlayalım dalış başlıyor.

PROBLEM

Yazılım geliştirmenin hem iyi hem de kötü olan özelliklerinden biri, içinde bulunduğu dinamizmdir. Her şey sürekli bir hızla değişir. Teknoloji, mimari yöntemler, hatta yaklaşımlar. Ama yapılan işlerin kendi süreçleri değişmez. Eğer bir satış uygulaması yazıyorsanız, sipariş süreci bellidir. Ya da bir rezervasyon, açık arttırma ya da borsa uygulaması geliştiriyor olsanız da aynı durum söz konusudur. Her işin kendi içinde değişmez bir atılması gereken adımlar sırası vardır.

Aslında, geniş bir bakış açısıyla bu durumun hemen her sektörde böyle olduğunu görebiliriz. Medeniyet tarihinin en eski mesleklerinden birini ele alalım mesela: Aşçılık. Evet aradan yüz bin yıllar geçmiş olsa da bu mesleğin tanımı hep aynıydı: Girdi olarak malzemeleri kullan ve çıktı olarak yenebilecek bir ürün oluştur. Herhangi bir yemek tarifini düşünün. Araya ne kadar teknoloji girmiş olursa olsun, elde ettiğiniz sonucu belirleyen şey kullandığınız malzemeler ve attığınız adımlardır.

Tamam biraz daha somut bir örnek gelsin. Banka hesabına girip para transferi yapmanın adımları bellidir.

  1. Giriş yap
  2. İşlemi seç
  3. Gönderilecek kişinin bilgilerini gir
  4. Gönderilecek tutarı gir
  5. Eğer bakiye uygunsa gönder

Kartınız veya gözünüzün retinası aracılığıyla giriş yapmış olmanız fark etmez. Bu adım olmadan işleminiz gerçekleşmeyecektir.

Bizim geliştirdiğimiz uygulamalarda da durum böyle değil mi a dostlar? Veritabanı ya da başka bir depo yapısı üzerinde bir sürü işlem yapıyoruz ve bu işlemler de kendi içinde aynı adımlara sahip. Hatta çoğu zaman, bu adımların kayıtlarını ve sırasını tutmamız gerekebiliyor. Ya da bir senaryoya göre bazı adımların işlemden geri alınabilir olması gerekebiliyor.

Acaba, uygulamamızın  değişebilen parçalarını değişmez alt yapısına nasıl adapte edebiliriz?  Gelin bu problemin çözümüne gerçek dünyada bakalım.

GERÇEK DÜNYADAN BİR ÖRNEK

Bir süper markette, kasanın çalışma mantığını düşünelim. Sepetinize almış olduğunuz ürünleri, kayan şeridin üzerine yerleştirirsiniz. Kasiyer, ürünleri sırasıyla barkod okuyucu üzerinden geçirir. Bu noktada, üründen hala vazgeçme ihtimaliniz vardır. Tüm ürünlerin barkodu okunduğunda, toplam fiyat belirlenir ve ödeme aşamasına geçilir. Burada yapının tamamı agnostik bir biçimde varolur.

Buradaki agnostik terimini biraz daha açalım. Agnostik kelimesinin doğrudan anlamı “bilinmezci”. Bu felsefi terimin mühendislikteki karşılığı ise şu: herhangi bir fiziksel parçadan bağımsız olarak çalışan yani sadece sonuca odaklanan çalışan mekanizma. Örneğin, ucu değiştirilebilen bir matkap düşünün. Burada matkabın dönen silindir kısmı agnostiktir. Döndüreceği ucun kalınlığını bilmesine gerek yoktur. Sadece döndürür o kadar. Ya da ucuna ampul bağlı olan bir duy da agnostiktir. Ampulün kaç Watt olduğu ve rengi duyun umurunda bile değildir.

Yani örneğimizdeki kasiyerin, ürünün ne olduğunu bilmesine gerek yoktur. Ürünler de fiyatlar ve hatta kasiyer de değişebilir. Ama süreç hep aynıdır.

ÇÖZÜM

Problemi anlamak için nasıl dünyanın en eski mesleklerinden birine baktıysak, çözümü de yerleşik hayatın eski oluşumlarından birinde bulabiliriz: Ordu. Özellikle modern zamanlarda, orduların işleyiş biçimleri yukarıda tanımladığımız problemi barındırırlar. Bu problemi çözmek için de sağlıklı bir sistemleri vardır. Yapılacak işlere yetki sahibi komutan karar verir. Kimin ne iş yapacağına dair bir emir hazırlar. Bu emri bir ast (ya da bir başka araç) aracılığı ile gerekli kişilere iletir. Buradaki emir taşıyıcısı için emri kimin verdiğinin ya da ne yapılacağının önemi yoktur. Bildiği tek şey, emrin kime ulaşması gerektiğidir. Bu emirler birkaç tane olabilir. Sıralı bir biçimde verilebilir. Ya da bazıları sonradan iptal olabilir. İsteneni ulaştırmak dışında emir ileticisinin başka yapabileceği yoktur.

O zaman çözümün parçaları belli oldu.

  1. Emiri alacak olan nesne inşa edilmeli.
  2. Her emrin ortak bir arayüzü olmalı
  3. Her emir, hangi nesnenin alıcı olduğunu bilmeli. Çünkü emir yalnızca alıcısı tarafından gerçekleştirilebilir olmalı.
  4. Emiri iletmesi için bir nesne inşa edilmeli. Bu nesne, emir dışında hiçbir şey bilmemeli.

İşte şimdi bu adımları oluşturma zamanı. Örneğimi mümkün olduğunca basit tutmaya çalışacağım şimdiden söyleyeyim. İşin geliştirilebilir yanlarını size bırakmayı niyet ediyorum.

 //1. Emri uygulayacak nesne
    public class SiparisIslemleri
    {
        public void YeniSiparisEkle() => Console.WriteLine("Yeni sipariş Eklendi");
        public void SiparisGuncelle() => Console.WriteLine("sipariş güncellendi");

    }
 //2. Çalıştırılacak her komutun standart bir arayüzü olmalı
    public interface IKomut
    {
        void Calistir();
    }

Üçüncü adımın anlaşılması önemli. Alıcı nesnenin yapacağı işleri doğrudan çağırmayacağız. Eğer öyle yaparsak değişecek her yapıda, bu metotlara müdahale etmemiz gerekirdi. Amaç zaten bundan kaçınmaktı. Öyleyse:

   public class SiparisEkleKomutu : IKomut
    {
        private SiparisIslemleri siparisIslemleri;
        public SiparisEkleKomutu(SiparisIslemleri siparisIslemleri)
        {
            this.siparisIslemleri = siparisIslemleri;
        }

        public void Calistir()
        {
            siparisIslemleri.YeniSiparisEkle();
        }
    }

Bu adımı geçmeden dikkat! SiparisEkleKomutu sınıfı, IKomut arayüzünü kullanıyor. Böylece Calistir() metoduna sahip. Ancak bu metot, alıcının ilgili metodunu çağırmaktan başka bir şey yapmıyor. Yani yalnızca emrin alıcısı emri uygulayabiliyor. Alıcı nesnemizin bir de “SiparisGuncelle” işi yapabildiğini görüyoruz. O zaman bir komut nesnesi de onun için oluşturalım.

  public class SiparisGuncellemeKomutu : IKomut
    {
        private SiparisIslemleri siparisIslemleri;
        public SiparisGuncellemeKomutu(SiparisIslemleri siparisIslemleri)
        {
            this.siparisIslemleri = siparisIslemleri;
        }
        public void Calistir()
        {
            siparisIslemleri.SiparisGuncelle();
        }
    }
  // Komut nesnesini alıcı nesneye iletecek nesne 
    public class VeritabaniKomutIletici
    {
        public void Calistir(IKomut komut)
        {
            komut.Calistir();
        }
    }

VeritabanıKomutIletici sınıfının tek yaptığına bakın! Aldığı komutun, calistir komutunu çağırıyor sadece. Fakat komut nesnesi de alıcı nesneye sahip. O da sadece alıcının ilgili metodunu çağırmakla yetiniyor.

İşte tüm bu komutları yönetmek için,  VeritabanıKomutIletici nesnesini kullanabiliriz.  Örneğin içinde bir IKomut listesi tuttuğunu ve çalıştırılan her komutu bu listeye eklediğini düşünün. Oldu mu size sırası takip edilebilir bir talep sırası… İster log tutmak için kullanın isterseniz makro oluşturmak. Bir eylemin iptal edilmesine mi ihtiyacınız var? Tek yapmanız gereken VeritabanıKomutIletici sınıfı içine bir geri al (undo) metodu yazmak ve gerekeni yapmak. İşte bu kadar.

Evet dostlar. Artık değişen süreçleri değişmeyecek mimarilere adapte etmenin en çok tercih edilen yollarından birini biliyorsunuz. Bu da bu yazının sonlanması için iyi bir nokta. Ha tabii, istemcinin emirleri nasıl verdiğini görmek de isteyebilirsiniz.

        private static void Main(string[] args)
        {   

            SiparisEkleKomutu siparisEkleKomutu = new SiparisEkleKomutu(new SiparisIslemleri());
            SiparisGuncellemeKomutu siparisGuncellemeKomutu = new SiparisGuncellemeKomutu(new SiparisIslemleri());

            VeritabaniKomutIletici veritabaniIslemcisi = new VeritabaniKomutIletici();


            veritabaniIslemcisi.Calistir(siparisEkleKomutu);
            veritabaniIslemcisi.Calistir(siparisGuncellemeKomutu);

            Console.ReadLine();
        }

Evet sevgili dostlar. Bu yazımızda Command Pattern’ini nerede kullanacağımız üzerinde konuştuk. Bu arada anlaşılmayan ya da beğenmediğiniz bir durum ile karşılaşırsanız lütfen yorum yapınız. Fakat yorum yaparken, eposta adresinizin doğru olduğuna dikkat edersiniz sevinirim. Yazıyı beğenmek zorunda değilsiniz (ki bazı yazıları ben de beğenmiyorum). Ancak, bana bunu ifade ediyorsanız sizinle diyalog kurmama da izin vermelisiniz.

Görüşmek dileğiyle efenim. Hoşça kalın!  

4 thoughts on “Command Design Pattern

  1. Hocam Main methodu içerisindeki parametrelerde aynı SiparisIslemleri nesnesini kullanması gerekmez miydi?

    SiparisEkleKomutu siparisEkleKomutu = new SiparisEkleKomutu(new SiparisIslemleri());
    SiparisGuncellemeKomutu siparisGuncellemeKomutu = new SiparisGuncellemeKomutu(new SiparisIslemleri());

    1. Evet. Elbette olabilirdi. Hatta iç içe sorgu (transaction) içeren durumlarda doğrudan böyle olmalıydı. Soru ve yorumun için teşekkürler.

  2. Hocam selamlar,
    ben bunu daha kolay kullanım şekli olan
    SiparisIslemleri.YeniSiparisEkle()
    şeklinde neden kullanmayıp, işi bu kadar uzattığımızı anlamıyorum. Bu konuyu biraz açabilir misiniz rica etsem

Leave a Reply