Thread overview
Command Design Pattern Gerçekten Gerekli Mi?
Apr 08, 2020
İbrahim
Apr 09, 2020
kerdemdemir
Apr 10, 2020
İbrahim
Apr 10, 2020
kerdemdemir
Apr 10, 2020
İbrahim
April 08, 2020

Merhaba;

Bir oyun farzedelim ve bu oyunda F'ye basınca ateş edeceğiz, G'ye basınca silah değiştireceğiz ve Space'e basınca da zıplayacağız. Bunun en basit ve doğal olan kodu şu şekilde:

void InputHandler::handle_input()
{
 if (is_pressed(BUTTON_F)) fire_gun(); // Ateş et.
 else if (is_pressed(BUTTON_G)) swap_weapon(); // Silahı değiştir.
 else if (is_pressed(BUTTON_SPACE)) jump(); // Karakteri zıplat.
}

Bunu command design pattern ile şu şekilde tasarlayabiliriz:

class Command
{
public:
 virtual void execute() = 0;
};

class FireCommand : public Command
{
public:
 virtual void execute() override
 {
   fire_gun(); // Ateş et.
 }
};

class SwapWeaponCommand : public Command
{
public:
 virtual void execute() override
 {
   swap_weapon(); // Silahı değiştir.
 }
};

class JumpCommand : public Command
{
public:
 virtual void execute() override
 {
   jump(); // Zıpla.
 }
};

class InputHandler
{
private:
 FireCommand* _buttonF;
 SwapWeaponCommand* _buttonG;
 JumpCommand* _buttonSPACE;

public:
 static Command* handle_input()
 {
   if (is_pressed(BUTTON_F)) return _buttonF;
   else if (is_pressed(BUTTON_G)) return _buttonG;
   else if (is_pressed(BUTTON_SPACE)) return _buttonSPACE;
 }
};

// Kullanımı:

int main()
{
 Command* command = InputHandler::handle_input();
 if (command)
 {
   command->execute();
 }

 return 0;
}

Sorum şu ki en başta da gösterdiğim gibi if - else ile hangi buton basıldıysa gerekli fonksiyonu / üye fonksiyonu çağırmak daha kısa ve anlaşılırken Command design pattern'a niçin ihtiyaç duyalım? Bunun bize tam anlamıyla faydası nedir? Birkaç örnek inceledim bu desen ile ilgili ama hiçbiri bu desenin gerekliliği hakkında bir fikir tam olarak oluşturmadı.

Teşekkürler.

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

April 10, 2020

Command patterni kitaplardan da okuyabileceğin gibi "redo,undo" gibi işlemler için çok faydalı. Bu command'ler aynı türden türediği için bir liste içinde saklayabiliyorsun. Sonra listeden geriye doğru dolaşıp her bir nesneye "undo" fonksiyonu çağırabiliyorsun.

Çok basit bir örnek yazmaya çalışıyım yarım yamalak olacak üşendiğimden kusura bakma :


#include <iostream>
#include <vector>


int val = 0; //=> kusura bakma çok kötü bir örnek oluyor sadece command'in geri almasına odaklan olurmu gerisi çok çöp

struct command
{
   virtual void execute( ) = 0;
   virtual void undo() = 0;
};

struct topla5 : public command
{
   void execute ( ) override { val += 5; };
   void undo () override { val -= 5; };
};

struct cikar5 : public command
{
   void execute ( ) override { val -= 5; };
   void undo () override { val += 5; };
};



int main()
{
   std::vector<command*> komutListesi {   new topla5(), new cikar5(), new cikar5() }; // normalde akıllı göstericiler kullanalım ben üşendim
   std::cout << val << std::endl;

   for ( auto elem : komutListesi )
       elem->execute();


   std::cout << val << std::endl;


   for ( auto elem : komutListesi )  // Kendim sonradan okurken gördüm bu yanlış tersten dönmem gerekirdi
       elem->undo();


   std::cout << val << std::endl;
}

Şimdi durumun 5 eklemek gibi değil ne biliyim kullanıcının biryerlere tıklaması gibi düşün. Her tıklama komplex bir işlem gerçekleştiriyor olabilir örneğin para ekliyor diyelim hesabımıza bir sürü database operasyonu vs... bu durumda "undo" operasyonun daha karışık olacaktır database op. geri alacaktır vs. karmaşık durumlarda gerçekten kullanışlı olabilir.

Ben çalıştığım projelerde; observer, decorater , adaptor, stragety, compsite patternlerini çok sık kullanıyorum. Ama gerçekte bunların bir kombinasyonu oluyor ortaya çıkan kod. Yani mantıklarını anlamanı ama kesinlikle ezberlememeni tavsiye ederim. Mantıklarıda çok çok basit örneğin adaptor bir interface'i başka bir interface'e uyarlamaktan başka bir şey değil özünde diğer patternlerde böyle. Onada 10 satırlık bir örnek verebilirim istersen.
Ama çok karışık bir anlatım varsa bu konularda kaçınmanı tavsiye ederim basit şeyler bunlar.

Kullanmaktan öte diğer yazılımcılarla iletişimimi çok kolaylaştırıyor. İşte destek aldığımız bir kütüphane var yine çalıştığım arkadaşlar tarafından geliştirilen. Daha ilk tasarım aşamasında anlaşmamız gerekirken yani ortada hiç bir şey yokken bu patternlerin ismini kullanarak anlatmak çok iyi oluyor. Mesela işte bu decorator olacak şunu decorate edecek gibi örneğin.

Umarım anlaşılır bir sekilde anlatabilmişimdir.
Erdem

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

April 09, 2020

Öncelikle, ne nesne yönelimli programlama ne de tasarım örüntüleri (design patterns?) eskiden ummulduğu kadar gözde yöntemler değil. Benim programlarımda 'interface' anahtar sözcüğü herhalde her iki programdan birinde geçiyor ve sıradüzenler yalnızca bir kaç sınıftan oluşuyor.

En önemlisi mühendis gibi düşünmek ve problemi çözmek; nasıl olursa olsun.

Command pattern'ın yararı gösterdiğin gibi programda hiç anlaşılmıyor çünkü burada hangi komutun işletileceğine karar verdikten hemen sonra işletiyoruz. Bu yöntem, komutların sonradan işletilmelerinin istendiği durumlarda yarar sağlar.

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

April 10, 2020

@alicehreli cevap için teşekkürler. Bilmiyorum ama okuduğum kitaplar tasarım desenlerine çok önem vermişler. Mesela Game Programming Patterns kitabı.

@kerdemdemir cevap için teşekkür ederim. Verdiğiniz bilgi çok iyi oldu. Command tasarım deseninin Undo-Redo bölümü olmayan uygulamaları da var. Mesela benim okuduğum kitapta örnek olarak bir GameActor sınıfı oluşturulmuş ve bu sınıf oyundaki karakterimizi gösteriyor. Daha sonra şu şekilde kullanılmış:

unique_ptr<GameActor> actor = make_unique<GameActor>();
Command* command = InputHandler::handle_input(key);
if (command)
 command->execute(actor.get());

Yani direkt kullanıcının bastığı tuşu handle_input'ta yakalıyor ve basılan tuşa göre geriye JumpCommand ya da FireCommand nesnesi döndürülüyor. Ardından Command'ı kalıtan sınıfların execute fonksiyonları GameActor türünden nesne aldığı için execute fonksiyonu ilgili GameActor'un jump() ve fire_gun() fonksiyonlarını çalıştırıyor.
İlerki sayfalarda Undo-Redo kısmını da eklemiş. Buna örnek olarak da karakteri mesela sağa 5px hareket ettirip daha sonra adımı geri alıp eski konumuna getirmeye çalışmış. Bunu da undoMove fonksiyonu ile yapmış.

Daha önce okuduğum bir Android kitabında da OptionMenu için şöyle bir kod verilmiş:

 @Override
 public boolean onOptionsItemSelected(MenuItem item)
 {
   switch (item.getItemId()) {
     case R.id.miOpen:
       this.setTitle("'Aç' seçeneğini seçtiniz!");
       break;
     case R.id.miSave:
       this.setTitle("'Kaydet' seçeneğini seçtiniz!");
       break;
     case R.id.miSaveAs:
       this.setTitle("'Farklı Kaydet' seçeneğini seçtiniz!");
       break;
     case R.id.miCut:
       this.setTitle("'Kes' seçeneğini seçtiniz!");
       break;
     case R.id.miCopy:
       this.setTitle("'Kopyala' seçeneğini seçtiniz!");
       break;
     case R.id.miPaste:
       this.setTitle("'Yapıştır' seçeneğini seçtiniz!");
       break;
     case R.id.miExit:
       this.setTitle("'Çık' seçeneğini seçtiniz!");
       break;
   }
   return super.onOptionsItemSelected(item);
 }

Yazar bu koda saçma bir tasarım demişti. Daha sonra kendisi Command design pattern ile tekrar tasarlamış. Çok uzun bir koddu. Mesela File, Edit gibi menü seçenekleri var. File için ayrı sınıf, Edit için ayrı sınıf şeklinde yapmıştı. Aslında bakınca switch-case ile tek bir metodun içine yazdığımızda fonksiyon aşağıya doğru uzayıp gidiyor. Yazılım geleneğinde bir fonksiyon 10 satırı geçmemeli gibi bir kabul var (Clean Code). Fakat Command kullanınca da sanki çok fazla gereksiz sınıf ve interface oluşturuyormuşuz algısına kapılıyorum. Düşünsenize her davranış için ayrı sınıflar oluşturuyoruz.

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

April 10, 2020

Alıntı:

>

Mesela benim okuduğum kitapta örnek olarak bir GameActor sınıfı oluşturulmuş ve bu sınıf oyundaki karakterimizi gösteriyor.

İbrahim eğer komutları bir listeye doldurmayacaksa neden kullanır ben çözemedim.
Bu arada 90'lı yıllarda OOP herşeyi çözer kanseri bile yener gibi algı vardı eğer okuduğun kitap eski ise ondan kaynatlanabilir.

Alıntı:

>

İlerki sayfalarda Undo-Redo kısmını da eklemiş.

En sonunda Undo-Redo eklemiş ama. Acaba ilk başta bunun için yapmamış gibi gözükmesi kitap okuma sırasının biraz kötü olmasından kaynaklanabilir mi?

Ya böyle artist-artist kitabı eleştirir gibi oldum ama design-patterns konusunda biraz insanların işi karışıklaştırdığını düşündüğümden ön yargılıyım. En önemli olan senin kafana yatması. Eğer kafana yatmıyorsa şimdi yaptığın sorgulamanı tavsiye ederim. Her meslekte olduğu gibi bizim mesleğimizde de işini olduğundan daha karmaşık göstermeye eğilim olabiliyor.

Alıntı:

>

Yazılım geleneğinde bir fonksiyon 10 satırı geçmemeli gibi bir kabul var

Evet, 10 gibi sayılara takılmamanı ama sonuçta senden sonra birileri okuyacak olduğunu düşünerek bakmanı tavsiye ederim. Sonuçta 10 satır yapacam diye 100 tane dosya 1000 sınıf oluşturursak oda anlaşılmaz. İlk başta tasarımı iyice düşünürek bunun güzel bir orta noktasını bulmak. Ben UML diagramlarını kullanıyorum ilk tasarımlarımı yaparken benim için faydalı oluyor.

Bu fonksiyon uzunluğu benim çok gıcık olduğum bir konu biraz içimi dökeyim bitireyim. Şimdi bu C++11 ile artık lambdalar tanımlayıp serbest fonksiyon(düz basit C fonksiyon) tanımlamıyabiliyoruz. İnsanlar sırf lambda kullanıp havalı olmak için bir fonksiyonun içine 20 tane lambda dolduruyorlar. Bu durumda fonksiyon oluyor yüzlerce satır. Ben scroll up-down yaparken midem bulanıyor.

Erdem

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

April 10, 2020

Alıntı:

>

En sonunda Undo-Redo eklemiş ama. Acaba ilk başta bunun için yapmamış gibi gözükmesi kitap okuma sırasının biraz kötü olmasından kaynaklanabilir mi?

Hocam dediğim kitap şu: https://gameprogrammingpatterns.com/
İçeriği de online okuyabiliyoruz. Command içeriği şöyle: https://gameprogrammingpatterns.com/command.html

Mesela basılan tuşlara göre işlem yapacağımız vakit sanırım en ideal yolun şu olduğunu düşünebiliriz. Çünkü mantığı basit, kullanıcı F'ye basarsa ateş et, G'ye basarsa zıpla vs. Yani tuş yakalamadaki algoritmada command kullanmamıza gerek yok diyebiliriz o halde (GLFW örneği):

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
   if (key == GLFW_KEY_F && action == GLFW_PRESS)
       fire_gun();
   if (key == GLFW_KEY_G && action == GLFW_PRESS)
       jump();
}

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]