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

7 comments:

Anonymous said...

พี่Pok เคยลอง SWT กับ eclipse rcp หรือยังครับผมซื้อหนังสือมานานแล้วอ่านไปได้นิดเดียวตอนนี้กำลังจะเริ่มศึกษาอีกครั้งหนึ่งเพราะอยากเอามาใช้งานร่วมกับ OFBiz

PPhetra said...

เคยตั้งท่าเขียน ERP ด้วย eclipse ไปครั้งหนึ่งแล้ว
ตอนนั้นแค่ทดลองแนวคิด และหาข้อจำกัดต่างๆ
ซึ่งก็เจออยู่พอสมควร เช่นเรื่อง class loader
ที่ีตีกับ spring, hibernate (สมัยนั้นนะ ตอนนี้ไม่รู้เป็นอย่างไรบ้าง)

ตอนนี้ที่กำลังดูเรื่อง binding อยู่
จริงๆต้นเรื่องมาจาก binding ที่พึ่งมีใน eclipse 3.3
แต่แวะออกนอนลู่นอกทางไปหน่อย
ก็เลยไปเริ่มที่ smalltalk

roofimon said...

"many ways to do one thing"
เหมือน ruby เลยว่าแล้วก็รอพี่มารักษาโรคกลัว ruby ครับ

Anonymous said...

ผมเป้นโรคอยากลอง Python ครับ

Wiennat said...

รู้สึกว่าโค้ดมันย้าวยาว แต่ทำอะไรได้นิดเดียวเอง

Anonymous said...

แค่ต้อง extend class Model ผมก็ไม่ชอบแล้วอะ

JGoodies โปรโมต MVP, Presentator Pattern ด้วยซึ่งก็ดีในแง่ของการจัดระเบียบโค้ดให้มัน maintain ง่าย

แต่กลายเป็นว่า framework มี learning curve สูงขึ้นและ productivity ตกลง

PPhetra said...

dean: แวบแรกของการ extend Model
ผมก็รู้สึกตะหงิดๆเหมือนกัน
แต่ก็ฉุกคิดว่า
"การที่เราชอบหรือไม่ชอบ กรณีที่ต้อง extend Model"
ไม่รู้ว่ามันได้อิทธิพลมาจากกระแส POJO แค่ไหน
อย่าง smalltalk ตัว domain ที่จะนำมาใช้ใน ValueModel
เขาก็แนะนำให้ extend Model เหมือนกัน
(สมัครใจไม่ใช้ก็ได้ แต่อาจะได้ของขวัญเป็น memory leak แทน)
ซึ่งใน smalltalk ยังไม่เคยได้ยินกระแส POJO นะ

ถ้าไม่ใช้ pojo ก็ต้องใช้ aspect แทน
ซึ่งเข้าทาง project ของ dean พอดี
สำหรับคนอื่นที่ยังไม่คุ้น aspect ลองอ่านเรื่อง bound properties ด้วย aspectj ด้ที่นี่
http://pphetra.blogspot.com/2005/11/implement-bound-properties-with.html