June 27, 2018

Command Pattern



Behavior Pattern အမျိုးအစားတစ်မျိုးဖြစ်ပါတယ်။ အရင်ဆုံး Command Pattern ကို အသုံးပြုပြီး ဖြေရှင်းနိုင်တဲ့ ပြဿနာတွေကနေ စပြီး လေ့လာကြည့်ကြရအောင်။

Problem

ကျွန်တော်တို့ GUI Application တစ်ခုကို ရေးသားနေပြီး၊ Button Class တစ်ခုကို ရေးသားဖို့လိုအပ်လာပြီဆိုကြပါစို့။ Button ကို Tools Bar မှာလဲ ထားမယ်။ ပြီးတော့ View Form တွေမှာလဲ ထားနိုင်မယ်။ ပြီးတော့ အဲ့ဒီ Button Object ကို အသုံးပြုပြီး copy လုပ်တာတွေ၊ cut လုပ်တာတွေပြီးတော့ Form ကို Submit လုပ်တာတွေ လုပ်ဆောင် စေလိုတဲ့အခါလဲရှိပါမယ်။ ကဲ ဒါဖြင့်ရင် ဘယ်လို ရေးကြမလဲ။ Button Class ထဲမှာ Label အတွက် Text လိုမယ်၊ ပြီးတော့ Icon ပါနိုင်တယ်၊ ပြီးတော့ အဲ့ဒီ Button ကို UI မှာ ဘယ်လို ဖေါ်ပြမလဲ ဆိုတဲ့ draw() ဆိုတဲ့ behavior လဲပါမယ်။ ပြီးတော့ အဲ့ဒီ button ကို Click နှိပ်ရင် ဘာလုပ်မယ်ဆိုတာကို ဖေါ်ပြနိုင်ရမယ်။ ပြီးတော့ Button အပေါ်မှာမူတည်ပြီး အလုပ်လုပ်ပုံတွေတူမှာမဟုတ်ဘူး။ ဒီနေရာမှာ စဉ်းစားမိမှာကတော့ “အမျိုးအစားတော့တူတယ်၊ အလုပ်လုပ်ပုံချင်းတော့မတူဘူး။ Polymorphism ကို သုံးနိုင်တယ်။”​ ဆိုတာပဲ ဖြစ်ပါမယ်။

ရေးကြည့်ကြရအောင်။ Polymorphism အတွက် Abstract Class ကို ရေးထားမယ်။ ပြီးမှ ဘုံအနေနဲ့ အသုံးပြုချင်တာတွေကို အဲ့ဒီ Class ထဲမှာရေးထားပြီး အလုပ်လုပ်ပုံချင်းမတူတဲ့ click() ကိုတော့ Abstract Method နဲ့ ရေးထားမယ်ပေါ့။ အိုကေ ဒါဆိုရင် သင်တန်းမှာ သင်ထားတဲ့ Abstraction ကော Polymorphism ပါ အဆင်ပြေသွားပြီ။
public abstract class Button {
 
 private String text;
 private String icon;

 public Button(String text, String icon) {

  this.text = text;
  this.icon = icon;
 }

 public void draw() {
  System.out.println("Button Text is " + text);
  System.out.println("Button Icon is " + icon);
 }

 public abstract void click();
}
Cut Button လိုချင်တယ်ဆိုရင်လဲ Button ကို Inheritance လုပ်ပြီး click method ကို Override လုပ်လိုက်ရုံပဲ။ ပြီးတော့ Past လဲ လာထား။ ဒါ့ထက်မကလို့ Send Button ဆိုလဲ နောက် Button Class တစ်ခုကို ရေးလိုက်ရုံပဲ။ နိပ်ဟ OOP! ဆိုပြီးတွေးပျော်နေမယ်ထင်ပါတယ်။
ဒါပေမဲ့ ဒီနေရာမှာ တွေးစရာ ၂ ချက်ရှိပါတယ်။ မတူညီတဲ့ Behavior တစ်ခုလိုတိုင်း Button တစ်ခုကို ရေးနေရရင် Button Class တွေအများကြီးဖြစ်လာနိုင်တယ်ဆိုတဲ့ အချက်နဲ့ တကယ်လို့ Cut Behavior ကို CTL + X နဲ့ ရေးချင်လာရင်လဲ Cut Behavior ကို Button မှာကော Short Cut Action မှာပါရေးနေရပါမယ်။ Don’t Repeat Yourself (DRY) ကို အကြီးအကျယ်ချိုးဖေါက်နေမိပါပြီ။ အဲ့ဒီလို ပြဿနာမျိုးကို Command Pattern ကို အသုံးပြုပြီး ဖြေရှင်းနိုင်မှာ ဖြစ်ပါတယ်။

Command Pattern

Command Pattern မှာ Command, Invoker, Receiver နဲ့ Client တို့ပါဝင်ကြပါတယ်။

ဒီနေရာမှာ အဓိက အရေးပါတာကတော့ Command Interface ပဲဖြစ်ပါတယ်။ ဘာလုပ်မှာလဲ ဆိုတာတဲ့ Business Logic ကို Wrap လုပ်ပေးနိုင်ပါတယ်။ အဲ့ဒီ Command Object ကို Invoker ကပိုင်ဆိုင်ပြီး ခိုင်းလိုတဲ့ အရာတွေကို အဲ့ဒီ Object ကို ခိုင်းမှာ ဖြစ်ပါတယ်။


အထက်ပါပုံအတိုင်း Client ကနေ Invoker ကို executeCommand() လို့ခိုင်းလိုက်တာနဲ့ Invoker ကနေ သူ့မှာရှိတဲ့ Command Object ရဲ့ execute() ကိုလုပ်ဆောင် ခိုင်းမှာ ဖြစ်ပါတယ်။ Command1 ကတော့ Command Interface ကို Implement လုပ်ထားတဲ့ Class ဖြစ်ပြီး Business Logic တွေကို ဆောင်ရွက်နိုင်တဲ့ Receiver Object နဲ့ အဲ့ဒီ Receiver Object ကို အလုပ်ခိုင်းရာမှာ အသုံးပြုနိုင်တဲ့ Parameter တွေကို State အနေနဲ့ ပိုင်ဆိုင်ထားပါတယ်။ တကယ်လို Invoker ကနေ execute() လို့ လုပ်ခိုင်းလိုက်ပြီဆိုတာနဲ့ Receiver Object ကို ပြန်ခိုင်းလိုက်ရုံပါပဲ။
How to Fix?


အိုကေ ဒီလို Pattern ကို အသုံးပြုလိုက်တာနဲ့ အထက်က ပြဿနာကို ဘယ်လို ဖြေရှင်းနိုင်မလဲ ဆိုတာကို လက်တွေ့ ကုဒ်လေးတွေရေးကြည့်ပြီး လက်တွေ့ ဖြေရှင်းကြည့်ကြရအောင်။

public interface Command {
 void execute();
}

ဒါကတော့ Button ကို click() နှိပ်တဲ့အခါမှာ အလုပ်လုပ်ပေးစေချင်တဲ့ Interface ဖြစ်တယ်။ ပြီးတော့ method တစ်ခုသာပါဝင်ပြီး Command အမျိုးမျိုးအတွက် တူညီတဲ့ ခေါ်ဆိုမှု့ကို သတ်မှတ်ပေးနိုင်ပါတယ်။ Copy Command ကို ရေးချင်တယ်ဆိုရင်လဲ ဒီ Interface ကို Inpmement လုပ်ပြီးရေးရမယ်။ Cut ဆိုရင်လဲ ဒီ Interface ကို Implement လုပ်ရမှာပါပဲ။ ဒါမှသာ Button Class ထဲကနေ ခိုင်းချင်တဲ့ အရာရှိရင် execute() method တစ်ခုကိုပဲ ခေါ်ပြီး ခိုင်းလိုက်ရုံပါပဲ။

public class Button {
 
 private String text;
 private Command command;

 public (String t, Command c) {
  text = t;
  command = c;
 }

 public void click() {
  command.execute();
 }
}

Button Class ကတော့ Command Pattern ရဲ့ Invoker နေရာမှာ တာဝန်ကျပါတယ်။ သူ့ဆီမှာတော့ Command ကို လုပ်ဆောင်ပေးနိုင်တဲ့ Command Object တစ်ခုရှိပါတယ်။ ဘာတွေလုပ်နိုင်တယ်ဆိုတာကိုတော့ Commannd Interface ရဲ့ နောက်ကွယ်မှာ Encapsulate လုပ်ပေးထားနိုင်ပါတယ်။ Button အနေနဲ့ သိစရာလိုတာကတော့ click() နှိပ်လာရင် Command ရဲ့ execute() ကို ခေါ်လိုက်ရုံပဲ ဆိုတာပါပဲ။

public class Editor {

 private String selectedText;
 
 public String getSelectedText() {
  return selectedText;
 }

 public void paste(String text) {
  selectedText = text;
 }
}

public class Clipboard {
 
 private String memo;

 public void save(String text) {
  memo = text;
 }

 public void getSaveData() {
  return memo;
 }
}

အထက်ပါ Editor နဲ့ ClickBoard တွေကတော့ Command Pattern ရဲ့ Receiver Class တွေပဲ ဖြစ်ကြပါတယ်။ သူတို့တွေကတော့ သက်ဆိုင်ရာ Command တွေကနေ ဆက်သွယ် အသုံးပြုကြမှာပါ။
public class CopyCommand implements Command {
 
 private Editor editor;
 private Clipboard clipboard;

 public CopyCommand(Editor e, Clipboard c) {
  editor = e;
  clipboard = c;
 }

 public void execute() {
  String text = editor.getSelectedText();
  clipboard.save(text);
 }
} 

ဆိုကြပါစို့။ Copy Command ကို ရေးပြီဆိုရင် Editor ထဲက Select လုပ်ထားတဲ့ Text ကို ယူပြီး Clipboard မှာ သွားပြီး Save လုပ်ပါမယ်။ တကယ်လို့ Clear Command မှာဆိုရင်လဲ Clipboard ထဲက memo ကို ဖျက်ပစ်ပါမယ်။ သက်ဆိုင်ရာ Command Concrete Object တွေက ဘယ် Receiver ကို သုံးမယ်ဆိုတာကို သိစရာလိုပါတယ်။

public class Application {
 
 private Button clearButton;
 private Button copyButton;

 public void init() {

  Editor editor = new Editor();
  Clipboard clipboard = new Clipboard();

  Command clear = new ClearCommand(clipboard);
  Command copy = new CopyCommand(editor, clipboard);

  clearButton = new Button("Clear", clear);
  copyButton = new Button("Copy", copy);

 }
}

အထက်ပါ Application Class ဟာ Command Pattern ရဲ့ Client နေရာမှာရှိပါတယ်။ Application Class ရဲ့ init method ထဲမှာ Receiver ဖြစ်တဲ့ Editor နဲ့ Clipboard Object တွေကို တည်ဆောက်ပြီး သက်ဆိုင်ရာ Command Object တွေကို တည်ဆောက်ပါတယ်။

Clear Button အတွက်ဆိုရင် ClearCommand Object ကိုပေးပြီတည်ဆောက်ပြီး Copy Button အတွက်ဆိုရင် CopyCommand Object တို့ကို ပေးပါတယ်။ ဒီလိုရေးသားထားတဲ့ အတွက် Short Cut တွေ Menu Item တွေမှာ အဲ့ဒီ Command တွေကို အသုံးပြုစေလိုတယ်ဆိုရင်လဲ ရေးထားပြီးသား Command တွေကို ပြန်ပြီး အသုံးပြုနိုင်ပါတယ်။ Button ထဲမှာသာ Business Logic တွေကို တိုက်ရိုက်ရေးသားထားခဲ့မယ်ဆိုရင် အဲ့ဒီ Logic တွေကို လိုချင်တဲ့ နေရာတိုင်းမှာ ရေးသားနေရမှာ ဖြစ်ပါတယ်။ အခုလို Command အနေနဲ့ ရေးသားလိုက်တဲ့အတွက် အဲ့ဒီ Command တွေကို အခြားနေရာမှာလဲ ပြန်ပြီး အသုံးချနိုင်တာ ဖြစ်ပါတယ်။ တဖန် Command ကိုသာ အသုံးမပြုထားခဲ့ရင် Invoker ဖြစ်တဲ့ Button က Receiver ဖြစ်တဲ့ Editor တို့ Clipboard တို့ကို တိုက်ရိုက်မှီခို အသုံးပြုနေရမှာ ဖြစ်တယ်။ ဤကဲ့သို့ Invoker နဲ့ Receiver တို့ကို Decouple လုပ်ပေးခြင်းအားဖြင့် လွတ်လွတ်လပ်လပ် ပြန်လည်အသုံးပြုနိုင်တဲ့ Component တွေကို ရေးသားစေနိုင်မှာ ဖြစ်ပါတယ်။

Because of Pattern

  • Operation တစ်ခုကို စတင်စေတဲ့ Object နဲ့ တကယ်အလုပ်လုပ်မည့် Object တို့ကို Decouple လုပ်ပေးနိုင်ပါတယ်
  • Open / Close Principle နဲ့ Single Responsibility Principle တို့ကို လိုက်နာစေပြီး Code တွေကို ပြန်လည် အသုံးချနိုင်အောင် ဆောင်ရွက်ပေးနိုင်ပါတယ်
  • ရှုပ်ထွေးတဲ့ လုပ်ဆောင်မှုတွေကို ရိုးရှင်းစွာ လုပ်ဆောင် စေနိုင်ပါတယ်

No comments:

Post a Comment