Command Pattern

Favori Design Pattern’larımdan biri olan Command Pattern nedir, ne işe yarar, hangi durumlarda kullanılır beraberce bakalım.Genelde yaygın olarak kullanılan Command Pattern oldukça faydalı kullanım alanlarına sahiptir. Çoğumuz kullanırken ne olduğunu farkemesek bile .NET, Java .. birçok dilde,kütüphanede uygulanmış bu kalıbı kullanırız. Bu tasarım kalıbının temel amacı değişen method çağırımlarını(method invocation) kodun diğer kısmından soyutlamakdır. Tarif biraz karmaşık oldu farkındayım. Bu yüzden hemen nerelerde ve nasıl kullanıldığına bakalım.

Mesela Java Swing kullananların bildiği AbstractAction sınıfları bir Command Pattern örneğidir.Birde kullanımına bakalım. Çok basit bir formumuz olsun içinde sadece tek bir buton nesnemiz olsun.Bu butonun içinde yapılan şeyler değişiklik gösteriyor.Bazen butona basıldığında Bize Merhaba mesajı göstermesini istiyoruz bazende formumuzu kapatmasını istiyoruz.Bu tarz bir gereksinimi Command Pattern kullanmadan klasik olarak buton kodunun içine if-else yazarak aşağıdaki gibi yapabilirdik.

public class CommandFrame extends JFrame {
    private JButton buton;
    private final String komutTipi;
    public CommandFrame(String komutTipi){
        this.komutTipi =komutTipi;
        setTitle("Command Pattern");
        setSize(300,200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        initButton();
    }

    private void initButton() {
        buton =new JButton("Calistir");
        buton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if(komutTipi.equals("Mesaj")){
                    JOptionPane.showMessageDialog(CommandFrame.this,"Merhaba");
                }else if(komutTipi.equals("Kapat")){
                    CommandFrame.this.dispose();
                }
            }
        });
        getContentPane().add(buton);
    }

    public static void main(String[] args) {
        CommandFrame frame =new CommandFrame("Mesaj");
        frame.setVisible(true);
    }
}


Gördüğünüz gibi yukarıdaki sınıfımızda if-else kontrolü yaparak her defasında kontrol yapıp neyin çalışması gerektiğini buluyoruz ve ardından onu çalıştırıyoruz. Tabi çalışması gereken şeyler bu kadar basit olmayabilir.Mesela kullanıcıdan yeni bir ihtiyaç geldi ve o butona basıldığında veritabanına yeni bir çalıştırıldı kaydı eklememizi istiyor.Peki ne yapacağız? İlk aklımıza gelen bir if-else daha ekleyip o if-else içine veritabanına bağlanıp kayıt ekleyen kodu aşağıdaki gibi yazmak olacaktır.

public class CommandFrame extends JFrame {
    private JButton buton;
    private final String komutTipi;
    private Connection con;
    private Statement stmt;

    public CommandFrame(String komutTipi) {
        this.komutTipi = komutTipi;
        setTitle("Command Pattern");
        setSize(300, 200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        initButton();
    }

    private void initButton() {
        buton = new JButton("Calistir");
        buton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (komutTipi.equals("Mesaj")) {
                    JOptionPane.showMessageDialog(CommandFrame.this, "Merhaba");
                } else if (komutTipi.equals("Kapat")) {
                    CommandFrame.this.dispose();
                } else if (komutTipi.equals("Kayit")) {
                    try {
                        Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                        con = DriverManager.getConnection("jdbc:odbc:Bus", "", "");
                        stmt = con.createStatement();
                        stmt.executeUpdate("Insert Into Tablo (Deger) VALUES ('Calisti')");

                        stmt.close();
                        con.close();
                    } catch (ClassNotFoundException e1) {
                        e1.printStackTrace();
                    } catch (SQLException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        });
        getContentPane().add(buton);
    }

    public static void main(String[] args) {
        CommandFrame frame = new CommandFrame("Kayit");
        frame.setVisible(true);
    }
}

Yukarıdaki CommandFrame sınıfına bakarsanız içinde yok yok olduğunu görecektiniz. Swing sınıflarından,JDBC kodlarına kadar herşeyi yazdık içine.Tabi artık bu sınıfımız birçok diğer sınıfa bağımlı hale geldi.Kodun karmaşıklığı arttı ayrıca diğer kısımlarda CommandFrame sınıfımıza kullanmak neredeyse imkansızlaştı. Çünkü her yeni eklenecek özellikte bu sınıfa yeni bir if-else yapısı ekleyip tekrar derlemek zorundayız.Burada hemen Command Pattern yardımımıza koşuyor.Zaten koda biraz üstten baktığımızda if-else yapıları içinde farklı olan şeylerin birşeyleri çalıştırmak olduğunu göreceksiniz.Aslında hepsinde farklı işler yapan bir metod çağırılıyor diyebiliriz. Command Pattern’ın özüde bu zaten. Her birinde farklı işler yapan o metodları koddan soyutlamak. Bunu Java’da interface kullanarak,.NET de delegate kullanarak yapabiliriz. Zaten bunun için Swing de daha önceden bu yapıya göre yazılmış AbstractAction sınıfı mevcut yapmamız gereken o sınıfdan yeni sınıflar türetip kodumuzu aşağıdaki gibi değiştirmek.

class MesajGosterAction extends AbstractAction {
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "Merhaba");
    }
}

class PencereKapatAction extends AbstractAction {
    private JFrame frame;    
    public void actionPerformed(ActionEvent e) {
        frame.dispose();
    }
    
    public void setFrame(JFrame frame) {
        this.frame = frame;
    }
}

class KayitEkleAction extends AbstractAction {
    private Connection con;
    private Statement stmt;

    public void actionPerformed(ActionEvent e) {
        try {
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            con = DriverManager.getConnection("jdbc:odbc:Bus", "", "");
            stmt = con.createStatement();
            stmt.executeUpdate("Insert Into Tablo (Deger) VALUES ('Calisti')");

            stmt.close();
            con.close();
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
    }
}

public class CommandFrame extends JFrame {
    private JButton buton;
    private AbstractAction komut;

    public CommandFrame(AbstractAction komut) {
        this.komut =komut;
        setTitle("Command Pattern");
        setSize(300, 200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        initButton();
    }

    private void initButton() {
        buton = new JButton("Calistir");
        buton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                komut.actionPerformed(e);
            }
        });
        getContentPane().add(buton);
    }

    public static void main(String[] args) {
        CommandFrame frame = new CommandFrame(new MesajGosterAction());
        frame.setVisible(true);
    }
}

Gördüğünüz gibi CommandFrame sınıfı artık JDBC kodlarından,Kapatma kodlarından ve gelebilecek herhangi bir istekten haberi olmayacak yani onlardan bağımsız. İleride başka bir şeyi çalıştırma,başka işlem yapma isteği gelse bile CommandFrame sınıfının içini değiştirmeyeceğiz.Sadece AbstractAction sınıfından türeyen yeni komut sınıfları oluşturmak olacak.Ayrıca if-else kontrollerinden de kurtulduk. Bu bize kodun okunulabilirliği açısından da büyük kazanç sağladı. Aklınızın bir köşesinde bulunsun eğer çok if-else yazıyorsanız tasarımınızda ve kodunuzda bir problem olduğunun göstergesidir.

Bir diğer kullanım örneği olarak Java’daki Thread yapısını vereceğim.Çok basit bir kod üzerinde konumuzu inceleyelim

class EkranaYaz implements Runnable{
    public void run() {
        System.out.println("Ekrana Yazıyorum...");
    }
}

class TaklaAt implements Runnable{
    public void run() {
        System.out.println("Takla atıyorum...");
    }
}

public class ThreadOrnek {
    public static void main(String[] args) {
        Thread thread =new Thread(new EkranaYaz());
        thread.start();
        
        Thread thread1 =new Thread(new TaklaAt());
        thread1.start();
    }
}


Main metoduna bakacak olursanız iki tane Thread oluşturduk birinde ekrana yazıyoruz diğerinde takla atıyoruz:) Tabi gerçek hayatta muhtemelen sadece ekrana yazmakla kalmayıp büyük ihtimalle işi halletmek için taklalar atacaksınız.Fakat koda dikkat edecek olursanız Thread sınıfının içinde hiçbir değişiklik yapmadık(Java’da yapamayız zaten) .Sadece oluştururken yapıcısına Runnable interface’ini gerçekleyen iki adet sınıf verdik. Burada Command Pattern işlevini Runnable interface’i görüyor.

public interface Runnable {
    public abstract void run();
}


Runnable interface’ini uygulayan sınıflar tek bir metodun içini doldurmak zorunda run metodu. Ama o metodun içinde ister ekrana yazı yazdıralım ister veritabanına bağlanalım Thread sınıfını hiçbir şekilde ilgilendirmiyor.Bu şekilde Thread sınıfı yaptığı işlem kodundan bağımsız yeniden kullanılabilir bir sınıf haline gelmiş.Gördüğünüz gibi bu şekilde metod çağırımı bir interface arkasında soyutlanarak Command Pattern uygulanıyor.

Genelde Command Pattern örneklerini inceleyecek olursanız genel yapının şu şekilde olduğunu görürsünüz. Bir adet çalıştırılacak komutu simgeleyen Command interface’i bulunur. Örnek olarak Action, IExecutable,Startable,Runnable … gibi. Ayrıca bu interface’lerde genelde bir adet execute(),run(),start()…. gibi metod bulunur. Bu interface’i uygulayan sınıflar o metodu kendileri içinde doldururlar.

Gördüğünüz gibi kullanımı gayet kolay ve pratik olan bu tasarım kalıbı sayesinde oldukça esnek bir yapıda kod yazabildik. Vakit bulabilirsem gerçek bir projede kullanılan bir örneği de eklemeye çalışacağım. Design Patterns serüvenimiz daha yeni başlıyor sayılır diğer Patternlar ile çok yakında sizlerleyiz. Bizi izlemeye devam edin :)

9 thoughts on “Command Pattern

  1. Sadullah

    merhaba Cihat,
    mesai arkadaşlarımdan biri, “yazılım mühendisliğinde çok iyi bir site buldum, adam çok güzel anlatmış, keşke birde kitap yazsa, bak linki bu” diyerek bana bu sayfanın linkini gönderdi :) çok iyi yapmış, gerçekten çok güzel anlatmışsın.
    dediğin gibi çoğumuz oop nin ne olduğunu öğrenemeden mezun olduk. şimdi seninde yardımlarınla gelişmeye başlayacağımı umuyorum.Les Brownun dediği gibi bir noktadan başlamak lazım :) bu arada geçenlerde Factory pattern i okumuştum. onunda temeli buna benziyordu yanlış hatırlamıyorsam,ikisinin arasındaki fark nedir? doğrusu ben daha başlangıçta olduğum için karıştırmış da olabilirim ama :P
    Diğer patternleri de sabırsızlıkla bekliyorum,
    iyi çalışmalar.

  2. Cosku Cimen

    bu arada gerçekten cok güzel bir örnek blmuşsun.3 5 satırla Threading sınıfının çalışma mantığı ve command patern kullanımı böylesine güzel bir örnekle anlatılabilirdi.internetde bulduğum yabancı ,türkçe en iyi örnek olduğunu söyleyebilirim.Siteni yakından takip etmeye devam edeceğim.Başarılar:)ve yeni bloglarının devamı dileğiyle

  3. M. Cihat Altuntaş Post author

    Yardım edebildiysem ne mutlu. Bu ara çok vakit olmuyor projelerden fakat Ocak’tan yazacaklarım listem baya kalabalık:) Bunun dışında sizinde konular,içerik,tarz hakkında katkılarınız olması benim açımdan oldukça iyi olur. Teşekkürler.

  4. Cosku

    yazını okuduktan sonra biraz dusundum .şu ana kadar surekli pattern ,principles,refactoring,konularına değindik.bence basit bir şekilde bir class inceleyebiliriz. karmaşık olup çok kısa classlar oluyor bazen.Böyle bir örneği senin eşliğinde incelemeyi isterim.eskilerden bir örneğim var. zamanında yazdığım masa sınıfı,
    mantık basit.hosuna giderse buna benzer bir kod inceleyip anlatmayı, seve seve yardımcı olurum.

    class MasaManager
    {

    private delegate void MasaManagerActiveEventHandler(Masa sender,MasaEventArgs e);
    event MasaManagerActiveEventHandler onMasaActivated;
    private int _Tablecurrent=9999;
    private int _Hovertable;
    private int _Masacount;

    private static MasaManager _instance;

    public static MasaManager instance()
    {
    if (_instance==null)
    {
    _instance=new MasaManager();

    }

    return _instance;

    }
    public List Masalistesi;
    public int ActiveMasaId
    {
    get { return _Tablecurrent; }
    set { _Tablecurrent = value; }
    }
    public int HoverMasaId
    {
    get { return _Hovertable; }
    set { _Hovertable = value; }
    }

    public int Masacount
    {
    get { return _Masacount; }
    set { _Masacount = value; }
    }
    private MasaManager() {

    }

    public Masa FindMasa(PictureBox curpic)
    {
    Masa bulunan= Masalistesi.Find(delegate(Masa dump)
    { return dump.MasaPicbox == curpic; });
    return bulunan;
    }
    public Masa Current
    {
    get
    {
    if (ActiveMasaId != 9999)
    {
    return Masalistesi[HoverMasaId];
    }
    else return null;

    }
    set {ActiveMasaId=FindMasa(value.MasaPicbox).MasaId ;
    }
    }

    public Masa this[int index]
    {get {return Masalistesi[index] ; }
    set{ Masalistesi[index]=value;}
    }
    public void Add(PictureBox MasaPicbox, int MasaId)
    {
    Masa temp = new Masa();
    temp.MasaId = MasaId;
    temp.MasaPicbox = MasaPicbox;
    Masalistesi.Add(temp);
    temp.onActivated+=new Masa.onActivatedEventHandler(temp_onActivated);
    }
    public void temp_onActivated(Masa sender, MasaEventArgs e)
    { onMasaActivated(sender, e); }
    }

    class Masa
    {
    public delegate void onActivatedEventHandler(Masa sender,MasaEventArgs e);
    public event onActivatedEventHandler onActivated;
    private bool _active=false;
    private PictureBox _MasaPicbox;
    private int _MasaId;
    public Masa() { }

    public PictureBox MasaPicbox
    {
    get { return _MasaPicbox; }
    set { _MasaPicbox = value;}
    }
    public bool active
    {
    get { return _active; }
    set { _active = value; }
    }
    public int MasaId
    {
    get { return _MasaId; }
    set { _MasaId = value; }
    }
    public void Activate(bool isActive)
    {
    if (isActive)
    { this.MasaPicbox.BorderStyle = BorderStyle.Fixed3D;
    this.active = true;

    onActivated(this,new MasaEventArgs() );
    }
    else
    {this.active=false;
    this.MasaPicbox.BorderStyle = BorderStyle.FixedSingle;
    }
    this.MasaPicbox.Refresh();
    }

    }
    }
    class MasaEventArgs : EventArgs
    { private int _ActivemasaId;

    public int ActiveMasaId
    {
    get { return _ActivemasaId; }
    set { _ActivemasaId = value; }
    }
    public MasaEventArgs()
    {}
    }

    veya bir yazında ado.net entity ile Nhibrinate karşılaştırması yapabilirsin. hız esneklik performans açılarından.

    asp.net methodları ile javascript haberleşmesine de değinebiliriz.

    şimdilik aklımda bu kadar var. bunları begenmezsen başka fikirlerle de gelirim:)
    projelerinde başarılar

  5. Pingback: button1_click olay

  6. Emre SÜREN

    command pattern de;
    + komut (command) interface’i,
    + bunun en az bir implementasyonu,
    + bir alici (reciever),
    + bir tetikleyici (invoker)
    + bir de bunlari kullanacak client olmasi gerekiyor.

    burada verdiginiz iki ornekte de (AbstractAction ve Thread) reciever ve invoker yoklar.
    varsa hangileri aceba?

    verdiginiz orenkte bir interface, implementasyonu ve bunlari kullanan bir client var. buna design pattern demek dogru/mumkun mu?

    cogu patternde ya interface ya abstaction kullaniliyor. yani bir patterni ozel kilan sey kullanilmazsa baska birisine cok rahat benzeyebiliyor.

    su an gordugum kadariyla bu iki ornek, en fazla strategy ye benziyor.

Comments are closed.