Friday, August 10, 2007

alias_method_chain

เมื่อวานนั่งดู source code ของ Rails
ก็ไปพบกับเจ้า method aliase_method_chain

หลักการของมัน ก็ทำนองเดียวกัย around advise ของพวก AOP
ก็คือสมมติเรามี
class Payment
def say
"hi"
end
end


ถ้าเราต้องการเปลี่ยนพฤติกรรมของมัน
เช่นต้องการเติม prefix ข้างหน้า
ก็สามารถ re-open class ได้ดังนี้
class Payment
def say_with_prefix
">> " + say_without_prefix
end

alias_method_chain :say, :prefix
end

จากข้างบน alise_method_chain จะทำการ
เปลี่ยนชื่อ method เดิมจาก "say" -> "say_without_prefix"
แล้วก็ปรับให้การเรียกใช้ "say" ไปผ่านเข้าทาง "say_with_prefix" แทน

ข้างบนลองเขียนให้สั้นๆ
แต่ pattern ใน rails ที่เขานิยมเขียนกัน
เขาจะไม่เขียนอย่างนี้
เขาจะเขียนโดยใช้ module เข้ามาช่วย
โดยตั้ง namespace ให้เป็นระเบียบ
ดังนั้นในส่วน re-open class ข้างบน เขาจะเขียนใหม่เป็นแบบนี้แทน
(Note: ชื่อ module ก็แล้วแต่จะตั้ง แต่มีกี่ชั้นก็ว่าไป)
module PAYMENT
module AROUND
def self.included(base)
base.class_eval do
alias_method_chain :say, :prefix
end
end

def say_with_prefix
">> " + say_without_prefix
end
end
end

และเมื่อไรก็ตามที่ต้องการเปลี่ยนพฤติกรรมของ class Payment ก็ให้สั่ง
Payment.send(:include, PAYMENT::AROUND)


จริงๆแล้ว alias_method_chain นั้นไม่ได้ implement อะไรใหม่เลย
มันเป็นแค่ช่วยทำให้การเขียนมันดูสื่อ และเข้าใจง่ายขึ้น
เพราะถ้าเขียนแบบ ruby ทั่วๆไปที่ไม่ใช้ alias_method_chain ก็คือต้องเขียนแบบนี้

alias_method :say_without_prefix, :say
alise_method :say, :say_with_prefix

ผมว่า aliase_method_chain ดูเข้าใจง่ายกว่านะ

Related link from Roti

Thursday, August 09, 2007

redo, undo ใน visualwork (smalltalk)

http://www.cincomsmalltalk.com/files/bobw/screencasts/Refactoring_Steroids/

พึ่งรู้ว่า refactoring ใน smalltalk มันทำในขณะที่โปรแกรม(ที่ถูก refactor) กำลังงานอยู่ได้
โดยไม่ต้อง start, stop โปรแกรมเลย
แถมยังทำ redo-undo ได้อีกด้วย (ไม่ต้อง restart โปรแกรมเช่นกัน)

เป็นสภาพแวดล้อมในการพัฒนาที่ดูดีมากเลย.

Related link from Roti

Monday, August 06, 2007

JGoodies binding.

วันนี้ลองเล่น JGoodies Binding จริงๆจังๆดูบ้าง
หน้าจอที่จะทดสอบ มีหน้าตาแบบนี้



ด้านซ้ายเป็น JList และด้านขวาเป็น edit panel

เริ่มด้วย Domain Model ก่อน
ก็คือ Person
public class Person extends Model {

private String name;
private double salary;

}

สังเกตว่า extends จาก com.jgoodies.binding.beans.Model
Note: ถ้า binding แบบง่ายๆ เราไม่ต้อง extend Model ก็ได้
แต่ถ้า feature ที่ใช้ซับซ้อนขึ้น มันบังคับให้เราต้อง extend Model.

แนวคิดหลัก ของ JGoodies (ซึ่งลอกมาจาก VisualSmalltalk อีกที)
ก็คือ ValueModel
GUI Widget จะไม่ access ข้อมูลผ่าน Domain Object ตรงๆ
แต่จะ set หรือ get ข้อมูล ผ่านทาง ValueModel

ตรงนี้อาจจะสับสนกับ Swing Model พวก TableModel, ListModel นิดหน่อย
ตรงที่ Swing model ก็มีวัตถุประสงค์ทำนองเดียวกันกับ Value Model
แต่ว่า Swing model ไม่ได้มีลักษณะเป็น universal model
ที่สามารถใช้ร่วมกันทั้งหมด ไม่ว่าจะเป็น widget ชนิดไหน แบบเดียวกับ ValueModel

ข้อดีของ Universal Model แบบ ValueModel
นอกจากจะทำให้การเขียน โปรแกรม เป็น pattern เดียวกันหมดแล้ว
ยังมีข้อดีในแง่ที่เราสามารถ นำ ValueModel มาต่อกันเป็น chain ได้
ทำให้เราสามารถทำ feature พวก Buffered, Convertor, Indirection ได้

กลับมาที่โปรแกรมเรา
เราจะออกแบบโปรแกรมของเรา โดยใช้ pattern Presentation Model (ใน VisualWork Smalltalk เรียก Aplication Model)
เริ่มด้วยการสร้าง PresentationModel ของเรา
public class ApplicationModel {
}


Person Object ของเรา จะเก็บอยู่ใน ArrayList แบบนี้
public class ApplicationModel {
private ArrayList<Person> persons = new ArrayList<Person>();

}


step ถัดไปคือ เตรียม ValueModel ที่จะใช้ binding กับ JList,
ตัว ValueModel ที่เราจะนำมาใช้ ห่อ persons ของเราก็คือ SelectionInList
ซึ่งออกแบบมาเพื่อใช้กับพวก container widget เช่น JList, JTable.
feature ของมันก็คือ มันจะช่วย provide ValueModel ของ Current selection,selection index
ทำให้เรานำไป binding กับ widget อื่นๆได้อีก
(ซึ่งในกรณีของเรา ก็จะนำ current selection ไป binding กับ edit widget ที่อยู่ด้านขวา)
public class ApplicationModel {
private ArrayList<Person> persons = new ArrayList<Person>();
private SelectionInList<Person> selectionInList;

public ApplicationModel() {
selectionInList = new SelectionInList<Person>(persons);
}

public SelectionInList getPersons() {
return selectionInList;
}
}


ในฝั่งของ GUI เวลาจะนำไปใช้ ก็จะทำประมาณนี้
public class MainFrame extends javax.swing.JFrame {

private ApplicationModel model = new ApplicationModel();

/** Creates new form MainFrame */
public MainFrame() {
initComponents();
Bindings.bind(jList1, model.getPersons());
}

...
}


step ถัดไป ก็คือการ bind current selection ใน JList เข้ากับ Editor Panel ที่อยู่ด้านขวา
ซึ่งในกรณีนี้ประกอบด้วย JTextField 2 ตัว สำหรับ name และ salary

เริ่มด้วยในฝั่ง ApplicationModel ก็เตรียม ValueModel ที่จะนำไป bind กับ JTextField
ตรงนี้เราสามารถทำได้หลายวิธี แต่วิธีที่เลือกใช้ก็คือ ใช้ com.jgoodies.binding.PresentationModel เข้ามาช่วย
(เพื่อที่จะได้ใช้ feature BufferedValueModel)
public class ApplicationModel {
private ArrayList<Person> persons = new ArrayList<Person>();
private SelectionInList<Person> selectionInList;
private PresentationModel<Person> person;

public ApplicationModel() {
selectionInList = new SelectionInList<Person>(persons);
person = new PresentationModel<Person>(selectionInList.getSelectionHolder());
}

public ValueModel getPersonName() {
return person.getBufferedModel("name");
}

public ValueModel getPersonSalary() {
return new DoubleToStringConvertor(person.getBufferedModel("salary"));
}

class DoubleToStringConvertor extends AbstractConverter {

DoubleToStringConvertor(ValueModel subject) {
super(subject);
}

public Object convertFromSubject(Object arg0) {
if (arg0 != null) {
return arg0.toString();
}
return null;
}

public void setValue(Object arg0) {
subject.setValue(new Double(arg0.toString()));
}

}
}

จาก code ข้างบน จะมีประเด็นที่น่าสนใจอยู่ 2 ประเด็นคือ
1. เราใช้ getBufferedModel ในการ return ค่า ValueModel ไปให้ GUI view
ซึ่งผลก็คือ การเปลี่ยนแปลงใดๆที่เกิดขึ้นที่ฝั่ง GUI View จะยังไม่มีผลต่อ Model
จนกว่าจะมีการสั่ง commit การเปลี่ยนแปลง
2. กรณีที่ Datatype ฝั่ง model ไม่ใช่ String ก็จะมีประเด็นเรื่องการ Convert Datatype
ซึ่งใช้เทคนิคการ chain ValueModel เข้าหากัน

ฝั่ง GUI View ก็สามารถนำไป binding ได้ดังนี้
Bindings.bind(jTextField1, model.getPersonName());
Bindings.bind(jTextField2, model.getPersonSalary());


ขั้นถัดไป ก็คือเตรียม Action ที่จะใช้เพื่อ commit การเปลี่ยนแปลงที่เกิดขึ้นใน BufferedValueModel

ฝั่ง ApplicationModel

public Action getApplyAction() {
return new AbstractAction("Apply") {

public void actionPerformed(ActionEvent arg0) {
person.triggerCommit();
selectionInList.fireSelectedContentsChanged();
}

};
}

ฝั่ง GUI View

jButton1.setAction(model.getApplyAction());


ปัญหาของ BufferdValueModel ก็คือ กรณีที่เกิดการแก้ขึ้นแล้ว
ยังไม่มีการ commit, ถ้า Selection เกิดเปลี่ยนไป ข้อมูลที่ bound ไว้
จะไม่ยอมเปลี่ยนตาม ดังนั้นเราต้องสั่ง cancel การแก้ไข ด้วยการเรียกใช้ method triggerFlush
selectionInList.addPropertyChangeListener(

SelectionInList.PROPERTYNAME_SELECTION,
new PropertyChangeListener() {

public void propertyChange(PropertyChangeEvent arg0) {
person.triggerFlush();
}

});


พอแค่นี้ก่อน ที่เหลือไม่ค่อยมีประเด็นแล้ว

Note.
1. แนวคิด + วิธีในการใช้งาน มีตัวเลือกให้ใช้มากเกินไป (many way to do one thing) ถ้า assign ให้เด็กใหม่เอาไปใช้ ไม่น่าจะรอด
2. กรณีที่มีการ refactor เปลี่ยนชื่อ property ของ Domain Model จะส่งผลกระทบเยอะ
เพราะ binding ใช้วิธีกำหนดชื่อของ property ในรุป String
3. รู้สึกว่า Groovy ก็มีใช้ pattern ValueModel ใน GUI ด้วยเหมือนกัน
(เจอใน gapi แต่ยังไม่เจอเอกสารที่พูดถึง)

Related link from Roti