13 Ocak 2017 Cuma

GoF - Command Örüntüsü

Command - Davranışsal Örüntü
Bu örüntünün benim gördüğüm iki kullanım şekli var.

1.Virtual Metod Şeklinde Birleştirilemeyen Farklı Nesneleri Bir Arada Kullanmak
Dispatch table aynı imzayı taşıyan virtual metodları gerçekleştirmek için kullanılıyor. Command Pattern bir çeşit dispatch table gibi düşünülebilir. Aradaki en önemli fark Command örüntüsü ile aynı imzanın kullanılmak zorunluluğunun ortadan kalkması. Yani farklı arayüzler sunan nesneleri bir arada kullanmak için bu örüntü ideal.

2.Aynı Nesne İçindeki If/Else'lerin Kaldırılması
Aşağıdaki gibi if/else veya switch/case'ler içeren metodlar  varsa bu örüntü kullanılabilir.
public void somethingHappened(int message){
  if(message == AN_INT_VALUE) doSomething();
  if(message == ANOTHER_INT_VALUE) doSomeThingElse();
  if(message == A_DIFFERENT_INT_VALUE) doAnotherThing();
}
Kim (Who) ve Ne Zaman(When) Soruları
İyi bir command örüntüsünde command nesnesi kim ve ne zaman sorularıyla ilgilenmez. Dolayısıyla command örüntüsü içinde popup kutuları çıkartmak gibi fikirler iyi şeyler değildir.

Klasik Örnek
Command örüntüsü için verilen klasik örnek, bir çok farklı modeldeki cihazı aynı arayüz ile kumanda edebilmek.

Command ve Event Arasındaki Fark
Command ve Event birbirlerine çok benzerler. Aralarında küçük bir fark bulunur. Anlamsal olarak Command bir cevap bekler

Örnek
Örneğin ShipOrder bir command olarak kabul edilirse bu emri gönderen başarılı veya başarısız oldu diye bir cevap bekler.

Event ise bir olay hakkında bilgi verdiği için cevap beklemez. Örneğin OrderShipped şeklinde bir event bilgilendirme amaçlıdır.

Örnek
Örneğin MakeTea bir command kabul edilirse bu emrin sonucunda NewTea gibi bir cevap gelmesi gerekir. Bunu takiben TeaMade şeklinde bir event'in yayınlanması gerekir.

Komutun Sonucunu Almak
Komutun sonucunu almak için ortak bir arayüz, data transfer object gibi bir şey kullanmak gerekiyor.
Eğer sonuç beklenmiyorsa şöyle kullanılabilir.
public abstract class Command {
  public abstract void execute();
}
Bazen Command'in çalışması sonucunda bir event üretilir. Bu durumda da event'leri ve command'leri işleyen ayrı bir katman gerekebilir. Bu katmana Prevalence Layer deniliyor. Bu katman önce bir önceki command'lerin çıktısı olan event'leri işler daha sonra da yeni command'leri işler şeklinde bir mantık kullanıyor.

Command ve Producer/Consumer
Command örüntüsü ile Producer/Consumer örüntüsü çok rahat birleştirilebilir.

Bir projede şöyle bir yapı kurduk.
1. Component 1
Ağdan yakalanan paketleri süz. Diğer bileşene ver.
Component1 ->Command Queue ->Component2
2. Component 2
Gelen paketleri sıkıştır ve kuyrukta sakla. Context modelleri saklayan yönetici sınıf.
Component2 ->Command Queue ->Context ->Models
Bu örnekte Component nesnesleri Active Object gibi davranıyor.  Gerekirse Active Object iki farklı kaynaktan beslenebilir.

Command ve Active Object
Active Object bir metod çağrısının başka bir thread içinde yerine getirilmesi demek. Command örüntüsü Active Object ile birleştirilebilir. Çok basit bir örnek şöyle. Önce klasik Command hiyerarşisi tanımlanır.
struct Command {
  virtual ~Command() {}
  virtual void exec() = 0;
}
class CopyCommand : public Command {
  const QString from, to;
  CopyCommand( const QString & from, const QString & to )
            : Command(), from( from ), to( to ) {} 
  void exec() { QFile::copy( from, to ); }
};
class MoveCommand : public Command {
        // ...
};
Sonra içinde thread ve kuyruk barındıran bir sınıf tanımlanır. Sınıfın copy ve move metodları birer command nesnesini kuyruğa eklerler. Kuyruğu boşaltan tek thread mesajları işler.
class MyActiveObject : public QObject {
    Q_OBJECT
public:
    explicit MyActiveObject( QObject * parent=0 )
        : QObject( parent ),
          thread(),
          queue(),
          mutex(),
          queueNotEmpty() {
        thead.start();
    }
    ~MyActiveObject() {
        // shut down thread:
        enqueue( 0 ); // end marker
        thread.wait(); // join with worker thread
    }
private:
    void enqueue( Command * cmd ) {
        const QMutexLocker locker( &mutex );
        queue.enqueue( cmd );
        queueNotEmpty.wakeOne();
    }
   void run() {
        while ( true ) {
            QMutexLocker locker( &mutex );
            while ( queue.isEmpty() )
                queueNotEmpty.wait( &mutex );
            Command * cmd = queue.dequeue();
            locker.unlock():
            if ( !cmd ) return; // end marker
            cmd->exec();
            delete cmd;
        }
    }
private:
    QThread thread;
    QQueue<Command*> queue;
    QMutex mutex; // protects 'queue'
    QWaitCondition queueNotEmpty;
};
Sınıfın metodları şöyledir.
public Q_SLOTS:
    void copy( const QString & from, const QString & to ) {
        enqueue( new CopyCommand( from, to ) );
    }
    void move( const QString & from, const QString & to ) {
        enqueue( new MoveCommand( from, to ) );
    }
Periyodik Yani Tick'lenen Active Object
Bazı projelerde Active Object nesnesinin her zaman çalışması istenmez. Bu nesne periyodik olarak - örneğin 1 saniye de bir - uyanır kuyrukta bekleyen işleri çalıştırır ve tekrar uyur.

Command ve History
Command tarihçesi için şöyle yaparız.
class CommandSession {
  private List<Command> commands = new ArrayList<>();
  private ListIterator<Command> scroller;

  public void execute(Command command) {
    scroller = null;
    commands.add(command);
    command.execute();
  }

  public Command scrollUp() {
    if (scroller == null) {
      scroller = commands.listIterator(commands.size());
    }
    if (scroller.hasPrevious()) {
      return scroller.previous();
    }
    return null;
  }
}





Hiç yorum yok:

Yorum Gönder