Komut Tasarım Deseni (Command Design Pattern)

Cem Topkaya
5 min readNov 13, 2019

--

Bu tasarım şablonunun adını “tilki tilkiye, tilkide kuyruğuna” diye çevirsek hiç abes kaçmayacak. Esasen OOD’un temelinde implementation’dan kaçınmak için arayüzler var diyebildiğimiz gibi, tasarım kalıplarının temelinde de aynı mantıkla coupling’in azaltılması var diyebiliriz.

İnsan beyni zincirin 8 halkasından sonrasını hatırlamakta zorlanıyor. Bu yüzden iteratif işler anlaşılması güç şeyler gibi geliyor. Telefon numaralarını hatırlamak zorunda kaldığımızda midemizde uçuşan kelebekleri hatırlayın. Tam da bu noktada COMMAND desenini anlamak bu yüzden biraz zorlaşıyor.

Bu Deseni Neden Kullanalım? (Motivasyonumuz)

İstemci kodumuz bir metot çağırsın (e tabi, bir iş yapması lazım). Bu metot hemen yanı başında veya bir sınıfın içinde yuvalanmış olabilir. İşte bu metodu doğrudan çağırmak bağımlılığı arttıracağı için COMMAND desenini kullanıyoruz. “Bir metot çağrısı için tasarım deseni mi olur?” diyorsanız nabız normal, beyin fonksiyonları çalışıyor demektir. OLMAZ TABİKİ! Ama bu desen metot sayısı arttığında, bir aracı üstünden çağırabilmeyi istediğimizde, işlemleri geri almak istediğimizde veya uzaktaki bir metodu çağırmak gerektiğinde (RPC) imdadımıza yetişiyor. Open/Closed prensipine uygun kod geliştirmek için harika bir tasarım deseni.

“Aracı üstünden çağırmak” konusunu biraz daha açalım. İstemciyle, çalıştırılmak istenen metodun bağlarını koparmak istersek araya bir aracı koyarız. İşte ilk arzumuz mümkün olduğunca bağları zayıflatmak. Böylece istemciyi değiştirdiğimizde, metot bundan etkilenmez, metodu değiştirdiğimizde istemci bu değişimden etkilenmez (hemen heyecanlanmayın çünkü bu bağları tam olarak kopartamayız ama zayıflatabiliriz).

İşte bağlantıları/ilişkileri zayıflatma işlemine “decoupling” ve aracı işini görecek nesneye de “invoker” diyoruz.

Şimdi “metot sayısı arttığında” konusunu işleyelim ama önce aynı çalıştırmak istediğimiz metodu tetikleyenlerden (triggers, listeners, events vs.) bahsedelim biraz. GUI üstünde bir işi düğme, menü, sağ tuşla açılan menü, kısa yol gibi çeşitli tetikleyicilerle yapmanız gerekebilir. Tüm bu işlemlerin sonunda hep aynı çıktıyı elde etmeniz gerekeceği için YA aynı komut kümesini (instruction set) tüm tetikleyicilerin altına yazmanız YA DA tüm tetikleyicilerin erişebileceği bir sınıfın içine bir metot açıp, bu komut kümesini metodun içine yazmanız gerekecek.

Aklımıza bir çapa bırakmak için artık bu sınıfa “alıcı/tetikleyici” (receiver/trigger) diyeceğiz.

“İşlemleri geri almak” konusuna nihayet gelebildik. COMMAND deseninin en yaygın kullanıldığı kısım bu başlıktır. Undo/Redo yapabilmek aslında işlemden etkilenen nesnelerin durumunu ileri/geri hareket ettirebilmek olarak düşünülebilir. Yani bir nesnenin durumunda (state) ileri geri gitmek istersek bize en uygun tasarım deseni; MEMENTO olacaktır. Ancak durum (state) temelli değil de süreci etkileyen işlemler penceresinden baktığımızda ise COMMAND deseni işimize çok yarayacaktır.

“Uzaktaki bir metodu çağırmak(RPC)” ise basitçe çalıştırmak istediğiniz metodun uzak sunucuda olması halidir.

Kodu bir metodun içine yazıp merkezileştirmek güzel fikir ama ya ileride bu kodu değiştirmek zorunda kalırsak!!! (korkan surat emojisi). İşte bu durum SOLID prensiplerimizin ‘O’ suna (Open/Closed Principle) karşı işlenmiş bir suça teşvik ediyor olacak.

O/C Prensibini aşağıdaki metinden okuyup ilahi bir emir gibi davranmak tıpkı kutsal bir kitaptan işine geleni alıp öncesi ve sonrasını yok saymaya benzer.

“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”

Robert C. Martin ayrıca şunu da söylüyor makalesinde:

It should be clear that no significant program can be 100% closed. … In general, no matter how “closed” a module is, there will always be some kind of change against which it is not closed.

Yani prensibin temelinde, %100 değişime kapalı bir kod yazmaya odaklanmak için genişleme (extension) düşünülerek bir yapı hazırlanmalı. Değişimin şart olduğu bir anda sinir krizleri geçirmeyiniz ve gereken değişimi yapınız. Özetle; O/C prensibinin istisnaları da vardır ve öyle her kod değişiminde extension yapmanız gerekmez :)

  • İlgili kodunuzda bir hata (böcek diyelim) varsa bunu düzeltmek için sınıfınızı genişletmeniz gerekmez. Kodunuzu düzeltin, testlerinizi elden geçirin ve her şeyin çalıştığından emin olun.
  • Ve tabiiki yaptığınız değişiklik yüzünden bu kodu çalıştıran harici kodlarda değişiklik gerekmeyecekse.

Receiver sınıfının içinde bir metodumuz vardı (şimdilik 1 diyelim ) ve bu metodun çalışma şeklini değiştirmemiz gerekti. Yeni bir sınıfı, Receiver sınıfımızdan türeterek genişletip ilgili metodumuzu ezdik (override) ve sadece nesne oluşturmak istediğimiz son sınıfımızın nesnesini istemcide yarattık.

Yapı Nasıl Olacak?

Önce “Beginning SOLID Principles and Design Patterns for ASP.NET Developers” kitabındaki UML çiziminden yola çıkarak tekrar rolleri tanıyalım.

Rolleri anlamak için komut desenini kullanabileceğimiz senaryolardan “İşlemleri geri almak” senaryosunu düşünelim. Receiver üstündeki metodu çalıştırmak isteyen istemci (client) önce kendine bir maşa yani invoker yaratıyor. Aynı zamanda istemcimiz, invoker’ın receiver üstündeki metoda erişebilmesi için command nesnesini yaratıyor.

Buraya kadar anladığımız; client tarafından verilen parametrelerle, command nesnesinin receiver üstündeki metodu çalıştırabilmesi için invoker nesnesi kullanılacak. Yani aşçı, bahçıvan, kahya, uşak denklemi gibi sıra sıra diziliyorlar.

  • İstemci, hedef metodu çalıştırmak için kendi yarattığı invoker nesnesinin Execute() yordamını çalıştırır.
  • İstemci, hedef metodu barındıran receiver sınıfından bir nesne yaratır.
  • İstemci, receiver sınıfındaki hedef metoda (varsa) gerekli parametreleri geçirebilmek ve metodu çalıştırabilmek için command nesnesini yaratır.
  • İstemci, yarattığı command nesnesine bağımlılık enjeksiyonu (DI) ile receiver nesnesini geçirir ki, command nesnesi hedef metotla tanışsın.
  • İstemci, invoker nesnesine command nesnesini DI ile geçirir ki, invoker.Execute() metodu içinden command.Execute() metodunu çalıştırabilsin.

Anlaşılacağı üzere istemci, receiver nesnesini -> command nesnesine geçiriyor, command nesnesini -> invoker nesnesine geçiriyor.

Burası çokomelli: Komut nesnesini yaratırken receiver nesnesi içinde çalıştırmak istediğimiz metodun ihtiyaç duyacağı parametreleri de komut nesnesine geçiriyoruz. Çünkü metodu tetikleyen komut nesnesi olacak. Tabi komut nesnesini çalıştırmak için Invoker nesnesi yaratıyor ve kırmızı düğmeyi Client nesnesinin eline veriyoruz.

Şu soruyu siz de soruyor olabilirsiniz.

Her şeyi client yaratıp birbirine bağlayacaksa neden invoker.Execute() yerine command.Execute() metodunu doğrudan çağırmıyor?

Soruyu biraz daha netleştirmek için katmanlara bir bakalım. 4 Katman var ve biz eğer Command nesnesini hem client içinde yaratıp hem de Execute() metodunu çağırmak için neden Invoker sınıfını yaratıyoruz (3 katmanda çözebilecekken).

GoF der ki:

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

Yine çokomelli bir yer: Invoker nesnesi, istemci için bir yönetici katman. Çalıştırdığımız komutları kuyruklayarak undo/redo yapmamıza imkan veren, yapılan işleri günlükleyen bir yönetici. Yani invoker sadece istemci adına not tutan, hemen veya zamanı geldiğinde komutları çalıştıran bir sekreter.

Artık İşleyişe Bakabiliriz

Kodlama kısmı zaten herkesin üstesinden gelebileceği bir konu. Aşağıdaki UML hem sahneyi görebilmenizi sağlayacak hem de referans aldığım adreste barındırdığı kodla yardımcı olacaktır.

Referanslar:

https://howtodoinjava.com/design-patterns/behavioral/command-pattern/

https://www.amazon.com/Beginning-Principles-Patterns-ASP-NET-Developers/dp/1484218477

https://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional-ebook/dp/B000SEIBB8

https://www.amazon.com/Node-js-Design-Patterns-server-side-applications/dp/1785885588/ref=redir_mobile_desktop?_encoding=UTF8&aaxitk=n.qy3xanlC3TB5m0AI5pJA&hsa_cr_id=8442376040001

--

--

Cem Topkaya
Cem Topkaya

Written by Cem Topkaya

Evlat, kardeş, ağabey, eş, baba, müzik sever, öğrenmek ister, paylaşmaya can atar, iyi biri olmaya çalışır, hakkı geçenlerden helallik ister vs.

No responses yet