- CSS Table Gallery
- Moving by Bicycle
- Spot The Fake Smile Experiment
***** ควรลองเป็นอย่างยิ่ง
Saturday, September 03, 2005
Link น่าสนใจ (2005-09-03)
Related link from Roti
Friday, September 02, 2005
ความหมายของคำว่า Maven
ใช้ Maven มาตั้งนาน พึ่งรู้ความหมายของคำนี้
ไปได้มาจาก David Geary 's Weblog ในหัวข้อเรื่อง Tipping Rails
ที่เล่าถึงประเด็นที่เขาเชื่อว่า Rails จะถึงจุด tipping point ในไม่ช้า
People who love to acquire knowledge on all sorts of topics and, perhaps more importantly, love to tell others about what they've learned (in fact, the word maven comes from Yiddish and means someone who accumulates knowledge).
ไปได้มาจาก David Geary 's Weblog ในหัวข้อเรื่อง Tipping Rails
ที่เล่าถึงประเด็นที่เขาเชื่อว่า Rails จะถึงจุด tipping point ในไม่ช้า
Related link from Roti
Ruby กับการ parse binary file
ช่วงนี้มี project ที่จะลองใช้ ruby parse wtmp file
และทำการ generate Image ที่แสดงลักษณะการใช้งานของ user ออกมา
(จริงๆแล้วมันไม่ต้องเวอร์ขนาดนี้ก็ได้ แต่อยากลอง feature ต่างๆดู)
เริ่มแรกสิ่งที่ต้อง solve ก็คือการ parse wtmp file
ขอยกตัวอย่างเฉพาะเจาะจงลงไปที่ wtmp file ที่ได้จาก AIX แล้วกัน
ใน AIX wtmp file จะมี record size = 648 byte
(AIX version ใหม่ ของเก่าขนาดเล็กกว่านี้เยอะ)
วิธีการที่ผมใช้ก็คือ อ่าน file ทีละ 648 byte เป็น string จากนั้นก็ส่งไป
ให้
ตัว
method นี้จะ return Array ของ object โดยเราต้องกำหนด template
ที่จะใช้ในการ parse
ความหมายของ template ที่ใช้
Link
Document ของ unpack method ใน String class
และทำการ generate Image ที่แสดงลักษณะการใช้งานของ user ออกมา
(จริงๆแล้วมันไม่ต้องเวอร์ขนาดนี้ก็ได้ แต่อยากลอง feature ต่างๆดู)
เริ่มแรกสิ่งที่ต้อง solve ก็คือการ parse wtmp file
ขอยกตัวอย่างเฉพาะเจาะจงลงไปที่ wtmp file ที่ได้จาก AIX แล้วกัน
ใน AIX wtmp file จะมี record size = 648 byte
(AIX version ใหม่ ของเก่าขนาดเล็กกว่านี้เยอะ)
วิธีการที่ผมใช้ก็คือ อ่าน file ทีละ 648 byte เป็น string จากนั้นก็ส่งไป
ให้
parseRecord ทำงาน
def parseFile(fileName)
list = Array.new
begin
File.open(fileName, 'r') do |file|
while (! file.eof)
tmp = parseRecord(file.read(648))
wtmp = Wtmp.new(tmp)
list << wtmp
end
end
rescue
end
return list
end
ตัว
parseRecord ก็จะใช้ method unpack เข้ามาช่วยmethod นี้จะ return Array ของ object โดยเราต้องกำหนด template
ที่จะใช้ในการ parse
def parseRecord(str)
str.unpack("A256A14A64x2Nnx6Nnna256x36")
end
ความหมายของ template ที่ใช้
- A256 -> return String ที่ตัดเอา white space ออกแล้ว
ส่วนค่า 256 ก็คือ ความยาวของ data ที่ต้องการอ่านขึ้นมา ในที่นี้คือ 256 byte - x2 -> skip ข้ามไป 2 byte
- N -> integer ความยาว 4 byte
- n -> short (2 byte)
Link
Document ของ unpack method ใน String class
Related link from Roti
Wednesday, August 31, 2005
เล่าให้ฟัง (48-08-31)
วันก่อนอ่านเจอตารางตัวเลข โครงสร้างแรงงานนอกระบบ
ในหนังสือมติชนรายสัปดาห์ มีจุดสะดุดตาที่หนึ่งก็คือ
อืมม์ อาชีพเรานี่ใช้ฝีมือปานกลางเอง
กลับไปเป็นวิศวกรไฟฟ้าดีกว่าแฮะ
ช่วงนี้อ่านหนังสืออยู่เล่มหนึ่งชื่อ "ท้าวทองกีบม้า"
ของสำนักพิมพ์มติชน ขอบรรยายให้ฟังคร่าวๆ พอเป็นกระสัย
ชื่อ "ท้าวทองกีบม้า" เป็นชื่อบรรดาศักด์ิของข้าราชสำนักฝ่ายใน
มีหน้าที่ประกอบอาหารหวาน
ที่มาของชื่อนี้ก็มีคนสันนิษฐานไว้หลายแนว
แต่ที่หนังสือเล่มนี้ขยายความ ก็คือ ชื่อนี้เป็นชื่อที่ได้มาจากชื่อ
"แคทเทอริน เดอ ทอร์ควิมา" ซึ่งเป็นภรรยาของ
เจ้าพระยาวิชาเยนทร์ (คอนแสตนติน ฟอลคอน)
ในสมัยสมเด็จพระนารายณ์
เธอเป็นลูกครึ่งโปรตุเกส กับ ญี่ปุ่น
สืบเชื้อสายมาจาก ออกญาเสนาภิมุข (ยามาดา)
ที่รับราชการในสมัยพระเอกาทศรถ
หลังจากความวุ่นวายในช่วงเปลี่ยนราชวงศ์ในช่วงนั้น
ฟอลคอนถูกกำจัด (อาจรวมถึงพระนารายณ์ด้วย)
ตัวท้าวทองกีบม้า ถูกนำตัวไปเป็นทาสในวัง มีหน้าที่ทำอาหาร
ซึ่งช่วงนั้นแหล่ะที่เป็นที่มาของขนมหวาน
ที่มีไข่และน้ำตาลเป็นส่วนประกอบ (เป็นลักษณะของอาหารโปรตุเกส)
เช่น ทองหยิบ ทองหยอด ฝอยทอง ทองโปร่ง ทองพลุ ขนมหม้อแกง
สังขยา ....
เกริ่นแค่นี้พอแล้วกัน เล่มจริงอ่านแล้วเพลินดี
ใครสนใจลองหาอ่านดู
แถมกลอนให้อีกนิด
ในหนังสือมติชนรายสัปดาห์ มีจุดสะดุดตาที่หนึ่งก็คือ
ประกอบอาชีพอิสระ
- มีฝีมือสูง (วิศวกร/สถาปนิก/เภสัช)
- มีฝีมือปานกลาง (โปรแกรมเมอร์, สอนพิเศษ, นักร้อง, นักแสดง)
- มีฝึมือทั่วไป (ช่างเสริมสวย, แผงลอย, สาธิตสินค้า)
อืมม์ อาชีพเรานี่ใช้ฝีมือปานกลางเอง
กลับไปเป็นวิศวกรไฟฟ้าดีกว่าแฮะ
ช่วงนี้อ่านหนังสืออยู่เล่มหนึ่งชื่อ "ท้าวทองกีบม้า"
ของสำนักพิมพ์มติชน ขอบรรยายให้ฟังคร่าวๆ พอเป็นกระสัย
ชื่อ "ท้าวทองกีบม้า" เป็นชื่อบรรดาศักด์ิของข้าราชสำนักฝ่ายใน
มีหน้าที่ประกอบอาหารหวาน
ที่มาของชื่อนี้ก็มีคนสันนิษฐานไว้หลายแนว
แต่ที่หนังสือเล่มนี้ขยายความ ก็คือ ชื่อนี้เป็นชื่อที่ได้มาจากชื่อ
"แคทเทอริน เดอ ทอร์ควิมา" ซึ่งเป็นภรรยาของ
เจ้าพระยาวิชาเยนทร์ (คอนแสตนติน ฟอลคอน)
ในสมัยสมเด็จพระนารายณ์
เธอเป็นลูกครึ่งโปรตุเกส กับ ญี่ปุ่น
สืบเชื้อสายมาจาก ออกญาเสนาภิมุข (ยามาดา)
ที่รับราชการในสมัยพระเอกาทศรถ
หลังจากความวุ่นวายในช่วงเปลี่ยนราชวงศ์ในช่วงนั้น
ฟอลคอนถูกกำจัด (อาจรวมถึงพระนารายณ์ด้วย)
ตัวท้าวทองกีบม้า ถูกนำตัวไปเป็นทาสในวัง มีหน้าที่ทำอาหาร
ซึ่งช่วงนั้นแหล่ะที่เป็นที่มาของขนมหวาน
ที่มีไข่และน้ำตาลเป็นส่วนประกอบ (เป็นลักษณะของอาหารโปรตุเกส)
เช่น ทองหยิบ ทองหยอด ฝอยทอง ทองโปร่ง ทองพลุ ขนมหม้อแกง
สังขยา ....
เกริ่นแค่นี้พอแล้วกัน เล่มจริงอ่านแล้วเพลินดี
ใครสนใจลองหาอ่านดู
แถมกลอนให้อีกนิด
ทองหยิบทิพย์เทียมทัด สามหยิบชัดน่าเชยชม
หลงหยิบว่ายาดม ก้มหน้าเมินเขินขวนใจ
...
ทองหยอดทอดสนิท ทองม้วนมิดคิดความหลัง
สองปีสองปิดบัง แต่ลำพังสองต่อสอง
(กลอนของ ร.1)
Related link from Roti
ทดลอง Implement Business Rule ด้วย Drools
โดยปกติเวลาเรา implement business logic เรามัก
จะเขียน code ลงไปตรงๆ โดยใช้พวก control statement เป็นหลัก (พวก if statement)
ถ้าเป็นโปรแกรมที่มี bussiness logic ซับซ้อน ก็คงจะต้อง
เคยปวดหัวกับ if block อันมหาศาล โปรแกรมพวกนี้
ส่วนใหญ่จะเกี่ยวกับเงินๆทองๆ เช่น Module Doctor Fee (พวกหมอในโรงพยาบาลเอกชน
จะมีวิธีการคำนวณเงินที่ซับซ้อนมาก) หรือ Module การคำนวณเงินลงทะเบียนของนักศึกษา
ในโปรแกรมใหญ่ๆ ที่อยู่บน Mainframe มีอยู่จำนวนหนึ่งที่
นิยม implement business logic ด้วย Rule Engine
ถามว่าช่วยลดความซับซ้อนได้แค่ไหน
อันนี้ตอบไม่ได้เหมือนกัน เพราะยังไม่เคยลองเลย
แต่เท่าที่ดู ในกรณีที่มี Rule เยอะมาก ปัญหาที่เกิดก็คือ
Rule จะเกิด conflict กันได้ง่าย
ต้องมี Debugger หรือ Test Case ดีๆ ไว้ช่วย
ในส่วนของ Java ผมสนใจ Rule Engine อยู่หลายตัวเหมือนกัน
ช่วงนี้ได้ลองใช้ Drools ดู ก็พบว่าเข้าท่าดีเหมือนกัน
ตัว Drools เองใช้ Charles Forgy's Rete algorithm
ลองมาดูตัวอย่างการ Implement กันดู
สมมติว่าเราจะใช้ Rule Engine ในส่วนของการ คำนวณ Discount
ของ Sale Order
เริ่มด้วยการ define Domain ก่อน ทำง่ายๆดังนี้

ส่วน business rule ที่จะ implement มีดังนี้
Drools ให้เรากำหนด Rule ผ่านทาง xml file
โดยรูปแบบของ file เป็นดังนี้
กฎข้อที่ 1 "ลูกค้าทุกคนที่ซื้อของ ในเบื้องต้นจะได้ส่วนลด 5 %"
เขียนได้ดังนี้
กฎข้อที่ 2 "กรณีที่เป็นลูกค้าภาคเหนือ จะให้ส่วนลด 7 %"
กฎข้อที่ 3 "กรณีที่ซื้อสินค้าเกิน 1500 บาท และซื้อเป็นเงินสด จะให้ส่วนลด 10 %"
กฎข้อที่ 4 "สินค้า "a-1" จะไม่มีส่วนลด"
ในการ run จะเขียน code ดังนี้
จะเห็นว่าขั้นแรก เราต้อง load Rule จาก xml file ก่อน
จากนั้นก็ทำการ initialize Working Memory ขึ้นมา
ส่วน DebugListener ที่ใส่เข้าไป ก็เพื่อจะให้ Drools dump debug information
ออกมาให้เราดู
จากนั้นก็เป็นการ assert Fact เข้าไป
สุดท้ายก็สั่ง fireAllRules เพื่อให้ Rule Engine เริ่มคำนวณผลลัพท์
ข้อที่ต้องระวัง ก็คือ Conflict ของ Rule
อย่างในตัวอย่างของเรา สมมติให้ Data ที่จะทดสอบมี nature ดังนี้
ถ้าดูที่ Rule ที่เรากำหนดขึ้นมา จะเห็นว่า
Conflict จะเกิดได้ที่ rule ข้อ 2 กับ ข้อ 3
โดยข้อ 2 จะให้ส่วนลด 7 % ส่วนข้อ 3 จะให้ส่วนลด 10 %
การ solve สามารถทำได้หลายวิธี
ในกรณีของเรา เราใช้การกำหนด salience เข้ามาช่วย
โดยให้ rule ข้อ 2 มี saliance = 20 ส่วนข้อ 3 = 10
ดังนั้น Drools จะตัดสินให้ข้อ 3 ชนะ (ค่าน้อยๆมันทำทีหลัง เลยชนะ)
เท่าที่ลองทดสอบดูในประเด็นเรื่อง performance
พบว่าขั้นที่กินเวลามากสุดก็คือตอนที่เรา load xml file
ใน case ที่ทดสอบดู พบว่า
ไว้คราวหน้าจะลอง integrate เข้ากับ Spring ดู
อ่านเพิ่มเติม
จะเขียน code ลงไปตรงๆ โดยใช้พวก control statement เป็นหลัก (พวก if statement)
ถ้าเป็นโปรแกรมที่มี bussiness logic ซับซ้อน ก็คงจะต้อง
เคยปวดหัวกับ if block อันมหาศาล โปรแกรมพวกนี้
ส่วนใหญ่จะเกี่ยวกับเงินๆทองๆ เช่น Module Doctor Fee (พวกหมอในโรงพยาบาลเอกชน
จะมีวิธีการคำนวณเงินที่ซับซ้อนมาก) หรือ Module การคำนวณเงินลงทะเบียนของนักศึกษา
ในโปรแกรมใหญ่ๆ ที่อยู่บน Mainframe มีอยู่จำนวนหนึ่งที่
นิยม implement business logic ด้วย Rule Engine
ถามว่าช่วยลดความซับซ้อนได้แค่ไหน
อันนี้ตอบไม่ได้เหมือนกัน เพราะยังไม่เคยลองเลย
แต่เท่าที่ดู ในกรณีที่มี Rule เยอะมาก ปัญหาที่เกิดก็คือ
Rule จะเกิด conflict กันได้ง่าย
ต้องมี Debugger หรือ Test Case ดีๆ ไว้ช่วย
ในส่วนของ Java ผมสนใจ Rule Engine อยู่หลายตัวเหมือนกัน
ช่วงนี้ได้ลองใช้ Drools ดู ก็พบว่าเข้าท่าดีเหมือนกัน
ตัว Drools เองใช้ Charles Forgy's Rete algorithm
ลองมาดูตัวอย่างการ Implement กันดู
สมมติว่าเราจะใช้ Rule Engine ในส่วนของการ คำนวณ Discount
ของ Sale Order
เริ่มด้วยการ define Domain ก่อน ทำง่ายๆดังนี้

ส่วน business rule ที่จะ implement มีดังนี้
- ลูกค้าทุกคนที่ซื้อของ ในเบื้องต้นจะได้ส่วนลด 5 %
- กรณีที่เป็นลูกค้าภาคเหนือ จะให้ส่วนลด 7 % แทน
- กรณีที่ซื้อสินค้าเกิน 1500 บาท และซื้อเป็นเงินสด จะให้ส่วนลด 10 %
- สินค้า "a-1" จะไม่มีส่วนลด (ไม่เอายอดไปคำนวณในส่วนลดรวม)
Drools ให้เรากำหนด Rule ผ่านทาง xml file
โดยรูปแบบของ file เป็นดังนี้
<?xml version="1.0"?>
<rule-set name="BusinessRulesSample"
xmlns="http://drools.org/rules"
xmlns:java="http://drools.org/semantics/java"
xmlns:xs
="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation
="http://drools.org/rules rules.xsd
http://drools.org/semantics/java java.xsd">
<!-- Import the Java Objects that we refer
to in our rules -->
<java:import>
pok.test.Order
</java:import>
<!-- Rule -->
</rule-set>
กฎข้อที่ 1 "ลูกค้าทุกคนที่ซื้อของ ในเบื้องต้นจะได้ส่วนลด 5 %"
เขียนได้ดังนี้
<rule name="ในเบื้องต้น ลูกค้าทุกคนได้ส่วนลด 5 %" salience="100">
<parameter identifier="order">
<class>pok.test.Order</class>
</parameter>
<java:condition>
1 == 1
</java:condition>
<java:consequence>
order.setPercentDiscount(5.0);
</java:consequence>
</rule>
</rule-set>
กฎข้อที่ 2 "กรณีที่เป็นลูกค้าภาคเหนือ จะให้ส่วนลด 7 %"
<rule name="ลูกค้าภาคเหนือได้ส่วนลด 7 %" salience="20">
<parameter identifier="customer">
<class>pok.test.Customer</class>
</parameter>
<parameter identifier="order">
<class>pok.test.Order</class>
</parameter>
<java:condition>
customer.getRegion() == Customer.NORTH
</java:condition>
<java:condition>
order.getCustomer() == customer
</java:condition>
<java:consequence>
order.setPercentDiscount(7.0);
</java:consequence>
</rule>
กฎข้อที่ 3 "กรณีที่ซื้อสินค้าเกิน 1500 บาท และซื้อเป็นเงินสด จะให้ส่วนลด 10 %"
<rule name="สังซื้อมากกว่า 1500 บาท และเป็นเงินสด ได้ส่วนลด 10 %" salience="10
">
<parameter identifier="order">
<class>pok.test.Order</class>
</parameter>
<java:condition>
order.getAmount() > 1500.0
</java:condition>
<java:condition>
order.getPayType() == Order.CASH
</java:condition>
<java:consequence>
order.setPercentDiscount(10.0);
</java:consequence>
</rule>
กฎข้อที่ 4 "สินค้า "a-1" จะไม่มีส่วนลด"
<rule name="สินค้า p-1 จะไม่ให้ส่วนลด" salience="0">
<parameter identifier="item">
<class>pok.test.OrderItem</class>
</parameter>
<java:condition>
item.getProduct().getProductCode().equals("p-1")
</java:condition>
<java:consequence>
item.setIncludeInTotalDiscount(false);
</java:consequence>
</rule>
ในการ run จะเขียน code ดังนี้
RuleBase businessRules = RuleBaseLoader.loadFromUrl(BusinessLayer.class
.getResource("DiscountRules.xml"));
WorkingMemory workingMemory = businessRules.newWorkingMemory();
// Small ruleset, OK to add a debug listener
workingMemory.addEventListener(new DebugWorkingMemoryEventListener());
workingMemory.assertObject(order);
workingMemory.assertObject(order.getCustomer());
for (Iterator iter = order.getItems().iterator(); iter.hasNext();) {
OrderItem item = (OrderItem) iter.next();
workingMemory.assertObject(item);
}
workingMemory.fireAllRules();
// Test Result
System.out.println("Discount = " + order.getPercentDiscount());
for (Iterator iter = order.getItems().iterator(); iter.hasNext();) {
OrderItem item = (OrderItem) iter.next();
if (! item.isIncludeInTotalDiscount()) {
System.out.println("\titem:" +item.getProduct().getProductCode() + "
is no discount");
}
}
จะเห็นว่าขั้นแรก เราต้อง load Rule จาก xml file ก่อน
จากนั้นก็ทำการ initialize Working Memory ขึ้นมา
ส่วน DebugListener ที่ใส่เข้าไป ก็เพื่อจะให้ Drools dump debug information
ออกมาให้เราดู
จากนั้นก็เป็นการ assert Fact เข้าไป
สุดท้ายก็สั่ง fireAllRules เพื่อให้ Rule Engine เริ่มคำนวณผลลัพท์
ข้อที่ต้องระวัง ก็คือ Conflict ของ Rule
อย่างในตัวอย่างของเรา สมมติให้ Data ที่จะทดสอบมี nature ดังนี้
- customer อยู่ในภาคเหนือ
- ซื้อของ 7000 บาท เป็นเงินสด
ถ้าดูที่ Rule ที่เรากำหนดขึ้นมา จะเห็นว่า
Conflict จะเกิดได้ที่ rule ข้อ 2 กับ ข้อ 3
โดยข้อ 2 จะให้ส่วนลด 7 % ส่วนข้อ 3 จะให้ส่วนลด 10 %
การ solve สามารถทำได้หลายวิธี
ในกรณีของเรา เราใช้การกำหนด salience เข้ามาช่วย
โดยให้ rule ข้อ 2 มี saliance = 20 ส่วนข้อ 3 = 10
ดังนั้น Drools จะตัดสินให้ข้อ 3 ชนะ (ค่าน้อยๆมันทำทีหลัง เลยชนะ)
เท่าที่ลองทดสอบดูในประเด็นเรื่อง performance
พบว่าขั้นที่กินเวลามากสุดก็คือตอนที่เรา load xml file
ใน case ที่ทดสอบดู พบว่า
- load xml -> ~1800 millisec
- assert -> ~76 millisec
- fireRule -> ~5 millisec
ไว้คราวหน้าจะลอง integrate เข้ากับ Spring ดู
อ่านเพิ่มเติม
Related link from Roti
Tuesday, August 30, 2005
Link น่าสนใจ (2005-08-30)
- Raycoaster
Caustics are the optical effects that occur when light gets bent several times by some refractive material. You can observe them for example on the bottom of a swimming pool or when light shines through a glass bottle. I find the resulting shapes very attractive so I have tried to create a tool which allows me to create and explore caustics or rather pseudo-caustics.
- An Illusrated Guide to IPSec
Article นี้ใช้การอธิบายแบบ Bottom up
ดูแล้วเข้าใจมากขึ้นเยอะเลย
Related link from Roti
Link น่าสนใจ (2005-08-29)
- SteP 10 Limited
site นี้มีบทความเกี่ยวกับการ Modeling ที่น่าสนใจทีเดียว
(ในแง่ของ Bussiness Application) - Implementors plugin (Eclipse Plugin)
ช่วยในการ navigate จาก interface ไปยัง Implement Class
หรือจะกระโดดจาก implement class, method -> interface ก็ได้
Related link from Roti
Monday, August 29, 2005
Tagging with Rails
Y.o.m.b.a.r เขียน mixin ไว้ตัวหนึ่งที่ช่วยให้ ActiveRecord
handle เรื่อง tag ได้ง่ายขึ้น
ดูตัวอย่างการใช้งาน
วิธีการใช้ ก็คือให้เอา source ไปใส่ไว้ใต้ lib directory
แล้วก็แก้ config/environment.rb เพิ่มคำสั่ง
handle เรื่อง tag ได้ง่ายขึ้น
ดูตัวอย่างการใช้งาน
class Photo < ActiveRecord::Base
acts_as_taggable
end
elephant = Photo.find(4437)
elephant.tag 'zoo animals nature'
elephant.tagged_by?('urban') # => false
elephant.tags.size # => 3
elephant.tag_names # => [ 'zoo', 'animals', 'nature' ]
วิธีการใช้ ก็คือให้เอา source ไปใส่ไว้ใต้ lib directory
แล้วก็แก้ config/environment.rb เพิ่มคำสั่ง
require 'tagggale'
Related link from Roti
เรียนรู้ CLISP
สั่งซื้อหนังสือเกี่ยวกับ RUBY, RAILS จาก amazon มาอาทิตย์กว่าแล้ว
มันยังไม่ shpping เลย ก็เลยไปเดินร้านหนังสือที่ fortune มา
ได้หนังสือ Practical Common Lisp มาเล่มหนึ่ง
หนังสือเขียนดีใช้ได้เลย ชอบตรงที่เขาไม่เริ่มต้นด้วยการอธิบาย syntax
หรือ language feature แต่พยายามแสดงให้เห็นเลยว่า ถ้าเขียนกับโจทย์จริงๆแล้ว
จะต้องเขียนอย่างไรบ้าง
Environment ที่เขาแนะนำให้ใช้ ก็คือ ใช้ emacs + slime
ซึ่งเท่าที่ลองใช้ดูก็ work ดีมาก เสียอย่างเดียว
ยังจำคำสั่ง emacs ไม่ได้ (การกด meta key ก็วุ่นวายไปนิด
ใน OS X ที่ผมใช้ มันต้องกด ESC ทำให้ต้องง้างนิ้วไกลไปหน่อย
)
ส่วนตัว CLISP ผมใช้ GNU CLISP
สำหรับคนที่ใช้ java ก็มี Armed Bear Common List
ตัวนี้เป็น ANSI Common Lisp ที่ run บน Java Virtual Machine
มีข้อเสียในการ build นิดหน่อย ก็คือ ต้องใช้ Common LISP ในการ build
มันยังไม่ shpping เลย ก็เลยไปเดินร้านหนังสือที่ fortune มา
ได้หนังสือ Practical Common Lisp มาเล่มหนึ่ง
หนังสือเขียนดีใช้ได้เลย ชอบตรงที่เขาไม่เริ่มต้นด้วยการอธิบาย syntax
หรือ language feature แต่พยายามแสดงให้เห็นเลยว่า ถ้าเขียนกับโจทย์จริงๆแล้ว
จะต้องเขียนอย่างไรบ้าง
Environment ที่เขาแนะนำให้ใช้ ก็คือ ใช้ emacs + slime
ซึ่งเท่าที่ลองใช้ดูก็ work ดีมาก เสียอย่างเดียว
ยังจำคำสั่ง emacs ไม่ได้ (การกด meta key ก็วุ่นวายไปนิด
ใน OS X ที่ผมใช้ มันต้องกด ESC ทำให้ต้องง้างนิ้วไกลไปหน่อย
)
ส่วนตัว CLISP ผมใช้ GNU CLISP
สำหรับคนที่ใช้ java ก็มี Armed Bear Common List
ตัวนี้เป็น ANSI Common Lisp ที่ run บน Java Virtual Machine
มีข้อเสียในการ build นิดหน่อย ก็คือ ต้องใช้ Common LISP ในการ build
Related link from Roti
Sunday, August 28, 2005
Link น่าสนใจ (2005-08-27)
Roumen 's Weblog เขาเขียนเล่าเรื่อง Usability Studies of NetBeans
อ่านเพลินดีเหมือนกัน ที่ตลกก็คือเขาเล่าว่า เขาสามารถบอกได้ว่า ใคร(คนที่มาทดสอบ)
เป็นคนที่ใช้ eclipse อยู่แล้วบ้าง โดยดูจากพฤติกรรมที่คนคนนั้นเห็น error
เท้าความนิดหนึ่ง ใน eclipse เวลาเกิด error จะมีรูปกากะบาทเกิดขึ้นด้านซ้าย
ส่วนใน netbeans ก็เช่นเดียวกันเป็นรูปกากะบาทเหมือนกัน
ถ้าเป็นคนที่ใช้ eclipse อยู่ เวลาเกิด error ก็จะมีพฤติกรรมเหมือนกันหมด
ก็คือใช้ mouse ไป click ที่กากะบาท ซึ่งแน่นอนใน Netbeans การทำเช่นนั้น
จะกลายเป็นการ add Breakpoint แทน ที่น่าสนใจก็คือพฤติกรรมนั้นค่อนข้างจะติดเป็นนิสัย คือ ถึงจะเรียนรู้แล้ว
ว่า click กากะบาทใน netbeans ไม่ได้เป็นการแสดง error แต่พอเกิด error ใหม่ ก็จะยัง
คงพยายาม click กากะบาทอยู่ดี
ฟังเขาเล่าถึงเรื่องนี้แล้วก็นึกถึงตัวเองเหมือนกัน เมื่ออาทิตย์ก่อน ก็ใช้ Netbeans
paint หน้าจอ UI เหมือนกัน (ปกติใช้ eclipse แต่ชอบ visual editor
ของ netbeans มากกว่า) จะหงุดหงิดมากเวลาเกิด error แล้วพยายาม click
กากะบาทแล้วดันกลายเป็นการ add Breakpoint แทนแล้วก็ไม่จำด้วย เกิดใหม่ก็เผลอไป click ใหม่
Extremely Successful Software
มีอยู่ part หนึ่งที่เขาพูดถึง Technology ที่เขาใช้
อืมม์ผมนี่โลกแคบเหมือนกันแฮะ ไม่รู้เลยว่ามีคนใช้ Lisp ใน Business Application
สงสัยต้องเรียนรู้ภาษานี้บ้างแล้ว
Gigayachts lure the mega-rich
อันนี้สำหรับผู้ที่ใช้ Oracle
จะได้รู้ว่า license ที่จ่ายไป สุดท้ายไปอยู่ที่ไหน
Ellison สั่งต่อเรือ yacht ความยาว 137 metres
ภายในประกอบด้วย 5 ชั้น, 82 ห้อง, ห้องเก็บ wine ขนาดเท่ากับ beach bungalow
มีเครื่องปั่นไฟที่ใหญ่พอสำหรับเมืองเล็กๆ
ราคาแค่ 453 millon dollars เท่านั้นเอง
ส่วนลำของ Paul Allen (co-founder of Microsoft.)
ถูกหน่อย แค่ 300 millon dollars
(Note: อ่านเจอในมติชนก่อน แล้วค่อยไป search หาใน internet)
อ่านเพลินดีเหมือนกัน ที่ตลกก็คือเขาเล่าว่า เขาสามารถบอกได้ว่า ใคร(คนที่มาทดสอบ)
เป็นคนที่ใช้ eclipse อยู่แล้วบ้าง โดยดูจากพฤติกรรมที่คนคนนั้นเห็น error
เท้าความนิดหนึ่ง ใน eclipse เวลาเกิด error จะมีรูปกากะบาทเกิดขึ้นด้านซ้าย
ส่วนใน netbeans ก็เช่นเดียวกันเป็นรูปกากะบาทเหมือนกัน
ถ้าเป็นคนที่ใช้ eclipse อยู่ เวลาเกิด error ก็จะมีพฤติกรรมเหมือนกันหมด
ก็คือใช้ mouse ไป click ที่กากะบาท ซึ่งแน่นอนใน Netbeans การทำเช่นนั้น
จะกลายเป็นการ add Breakpoint แทน ที่น่าสนใจก็คือพฤติกรรมนั้นค่อนข้างจะติดเป็นนิสัย คือ ถึงจะเรียนรู้แล้ว
ว่า click กากะบาทใน netbeans ไม่ได้เป็นการแสดง error แต่พอเกิด error ใหม่ ก็จะยัง
คงพยายาม click กากะบาทอยู่ดี
ฟังเขาเล่าถึงเรื่องนี้แล้วก็นึกถึงตัวเองเหมือนกัน เมื่ออาทิตย์ก่อน ก็ใช้ Netbeans
paint หน้าจอ UI เหมือนกัน (ปกติใช้ eclipse แต่ชอบ visual editor
ของ netbeans มากกว่า) จะหงุดหงิดมากเวลาเกิด error แล้วพยายาม click
กากะบาทแล้วดันกลายเป็นการ add Breakpoint แทนแล้วก็ไม่จำด้วย เกิดใหม่ก็เผลอไป click ใหม่
Extremely Successful Software
มีอยู่ part หนึ่งที่เขาพูดถึง Technology ที่เขาใช้
Interestingly, when we checked we found that a number of Fortune 1000 companies were using Lisp in critical applications such as on-line banking and airline reservation systems.
อืมม์ผมนี่โลกแคบเหมือนกันแฮะ ไม่รู้เลยว่ามีคนใช้ Lisp ใน Business Application
สงสัยต้องเรียนรู้ภาษานี้บ้างแล้ว
Gigayachts lure the mega-rich
อันนี้สำหรับผู้ที่ใช้ Oracle
จะได้รู้ว่า license ที่จ่ายไป สุดท้ายไปอยู่ที่ไหน
Ellison สั่งต่อเรือ yacht ความยาว 137 metres
ภายในประกอบด้วย 5 ชั้น, 82 ห้อง, ห้องเก็บ wine ขนาดเท่ากับ beach bungalow
มีเครื่องปั่นไฟที่ใหญ่พอสำหรับเมืองเล็กๆ
ราคาแค่ 453 millon dollars เท่านั้นเอง
ส่วนลำของ Paul Allen (co-founder of Microsoft.)
ถูกหน่อย แค่ 300 millon dollars
(Note: อ่านเจอในมติชนก่อน แล้วค่อยไป search หาใน internet)
Related link from Roti
Friday, August 26, 2005
ทดลอง acts_as_tree feature ของ ActiveRecord (Ruby on Rails)
สรุปให้ฟังสำหรับคนที่ไม่อยากลงรายละเอียด
ใน post นี้ ประกอบด้วย 3 ประเด็นก็คือ- Feature ของ ActiveRecord ที่ใช้ในการกำหนดความสัมพันธ์แบบ self relate
(link เข้าหาตัวเอง, tree structure)
acts_as_tree - Rails Migration Tool
เป็น framework เล็กๆที่ช่วยในการ maintain database structure
ช่วยให้เราสามารถ keep สถานะ table structure เราในลักษณะ version ได้
(ทำให้เราถอยหลังหรือเดินหน้าไปที่ version ที่ต้องการได้)
Active Record Migration - Model Unit TestCase
การเขียน TestCase สำหรับทดสอบการทำงานของ Model
Testing Your Models
Fixtures
Details
วันนี้จะทดลองเล่น rails โดยทดลอง model Object ที่ self relate เข้าหาตัวเอง(มีลักษณะเป็น Tree Structure)
โดยจะใช้ตัวอย่าง business object ที่ชื่อ Category
เริ่มด้วยการสร้าง Model file ที่ชื่อ
$PROJECT/app/models/category.rbซึ่งทำได้โดยสั่งคำสั่ง
script/generate model categoryให้เพิ่มเนื้อหาของ file เข้าไปดังนี้
class Category < ActiveRecord::Base
acts_as_tree :order => "name"
end
parameter
order เป็นการกำหนดการเรียงลำดับของ children node (ในตอน qurey)
เมื่อมี model แล้ว ก็ต้องทำการสร้าง Table ใน Database ด้วย
สำหรับการสร้าง table นั้น rails มี feature หนึ่งที่ช่วยให้เรา
maintain structure ของ table ในลักษณะ keep version ได้
feature นั้นก็คือ Migration
เริ่มด้วยการใช้ command
script/generate migration tableเพื่อทำการ generate file ที่ชื่อ $VERSION_table.rb ให้เรา
โดย file จะอยู่ใต้ directory $PROJECT/db/migrate
($VERSION จะใช้แสดง version number ที่จะ automatic เรียงลำดับขึ้นไปเรื่อยๆ)
กรณีนี้เราทำเป็นครั้งแรก ดังนั้นจะได้ file 1_table.rb
ให้เราทำการ edit file ให้มีเนื้อหาดังนี้
class Table < ActiveRecord::Migration
def self.up
create_table :categories do |t|
t.column :name, :string
t.column :parent_id, :integer
end
end
def self.down
drop_table :categories
end
end
ความหมายก็คือ สร้าง table ที่ชื่อ categories โดยมี
column name, parent_id (foreign key ที่ชี้เข้าหาตัวเอง)
และมี primary key บน column id (migrate default สร้างให้เอง)
Note: ความหมายของ down method ก็คือ
กรณีที่มีการย้อน version จะต้องใส่ script
ที่ช่วยในการ reverse สิ่งที่เราสั่งทำงานไปใน up method
จากนั้นเมื่อต้องการสั่งให้ migration script ทำงาน
ก็ให้ใช้คำสั่ง
rake migrateStep ถัดไปก็คือ การทดสอบว่า model ของเราทำงานได้ถูกต้องไหม
โดยการเขียน Test Unit ที่ชื่อ
categories_test.rb ไว้ใต้ directory$PROJECT/test/unitrequire File.dirname(__FILE__) + '/../test_helper'
class CategoryTest < Test::Unit::TestCase
fixtures :categories
def test_select
root = Category.find_first "name = 'root'"
s1 = root.children[0]
assert_equal @categories["sub1"]["name"], s1.name
end
def test_root
root = Category.new();
root.name = "root"
assert root.save
c1 = root.children.create("name" => "pok")
c2 = root.children.create("name" => "bunn")
assert_equal c1.parent, root
assert_equal c2.parent, root
end
end
Note: รูปแบบการเขียน testcase จะเหมือนกับ JUnit
ใน TestCase ที่เราเขียน จะเห็นว่ามีการกำหนด Fixtures ไว้ด้วย
(fixture ก็คือ set ของ Data ที่เราจะ populate ลง table ไว้ก่อนที่
จะเริ่มทำการทดสอบ)
โดยตำแหน่งของ fixture จะสร้างไว้ใต้ $PROJECT/test/fixtures
โดย Rails เปิดให้เราเลือกใช้ fixtures file ได้ 2 แบบก็คือ
- yaml
- csv (comma seperated)
กรณีของเราเลือกใช้ yaml เนื่องจากกรณีทีใช้ csv จะเกิดปัญหา
กับ ค่า null ใน column ที่เป็น integer
ตัวอย่าง fixtures ที่ใช้
root:
id: 1
name: root
sub1:
id: 2
name: sub1
parent_id: 1
sub2:
id: 3
name: sub2
parent_id: 1
ที่นี้ลองมาดูว่า สมมติว่าเราเพิ่ม feature การ maintain children count
ใน table categories ของเรา แล้วจะใช้ migration เข้ามาช่วย
update table structure ของเราได้อย่างไรบ้าง
ขั้นแรก ก็คือการปรับ model เรา โดยการเพิ่ม option เข้าไปใน acts_as_tree ดังนี้
class Category < ActiveRecord::Base
acts_as_tree :order => "name", :counter_cache => "true"
end
option counter_cache จะใช้ column categories_count ในการเก็บจำนวน
ของ children node ที่เป็นสมาชิกของ node นั้นๆ
จากนั้น ก็ generate migration script ขึ้นมา
โดยการสั่ง
script/generate migration addผลลัพท์ที่ได้ก็คือ file
$PROJECT/db/migrate/2_add.rbให้เราทำการ edit file เพิ่มเนื้อหาเข้าไปดังนี้
class Add < ActiveRecord::Migration
def self.up
# on mysql
# add_column :categories, :categories_count, :integer, default => 0
# on postgres
# postgres has no alter table add column with default value
# so we must do it manually.
add_column :categories, :categories_count, :integer
execute "alter table categories alter categories_count set default 0"
end
def self.down
remove_column :categories, :categories_count
end
end
Note: มี bug บน Rails ที่เกิดจาก Postgres Database ไม่ได้ implements
sql alter table with default value ทำให้เราต้อง manual set ค่า default
ตามหลังเข้าไปอีกที
จากนั้นก็สั่ง
rake migrate เพื่อทำการ update table strutureให้มี version ล่าสุดตาม script ใน migrate directory
Note
ตัว Migration เท่าที่ลองเล่นดู จะเกิดปัญหาขึ้นประปราย ซึ่งเท่าที่เจอก็เช่นพอเราสั่ง migrate เดินหน้าหรือถอยหลัง กับ script ที่มี syntax ไม่ถูกต้อง
จะเกิดปัญหา inconsistent ขึ้นใน meta data ที่ใช้เก็บข้อมูล current version
ทำให้เราต้องใช้ manual sql เข้าไปจัดการกับ meta data โดยตรง
Related link from Roti
Tuesday, August 23, 2005
Link น่าสนใจ (2005-08-23)
- Metro Arts and Architecture
เห็นแล้ว รถไฟฟ้าใต้ดินเราจืดสนิดเลย
เขาแบ่งกลุ่มดังนี้ (รู้แล้วจะได้ไช้เป็นเกณฑ์ในการมองกลับมาที่บ้านเราบ้าง)- พวก Consistently plan, user-friendly, good-looking
- พวกที่มี สถาปัตยกรรมโดดเด่น
- พวกที่ตบแต่งภายในโดดเด่น
- พวกที่มีลักษณะ "Palace for the people" กลุ่มนี้เป็นประเทศพวกอดีตสังคมนิยม
- พวกที่มี public art
ที่ผมชอบก็มี Tashkent, stockholm, Los Angeles, Lille, berlin - พวก Consistently plan, user-friendly, good-looking
- art.com
ดูเผินๆ ก็เป็น ui ที่เปิดโอกาสให้เราวาดรูปธรรมดา (interface สวยดีเหมือนกัน)
แล้วก็ให้คน submit รูปที่วาดเข้าไปได้
แต่ที่น่าสนใจจริงๆก็คือวิธีที่เขา record event ไว้
ทำให้เวลาดูรูปของคนอื่น(ที่ submit เข้าไปใน gallery)
เราก็จะเห็น process ที่เขา develop รูปเขาขึ้นมา
ซึ่งอันนี้น่าสนใจมาก
(จริงๆแล้วในแง่ software ก็น่าสนใจมากเช่นกัน
ถ้าต้องเขียนโปรแกรมให้ record ให้ได้แบบเขา
จะต้องเขียนอย่างไร)
อย่าลืมเข้าไปดู gallery (replay mode) - The Sky in a Room
- Lilac Chaser
เปิดเข้าไปแล้วจะเห็น วงสีที่กระพริบเปลี่ยนตำแหน่งอยู่ ให้มองที่จุดศูนย์กลาง
แล้วก็จะเห็นว่าปรากฎการณ์ “negative retinal afterimage”
Related link from Roti
Monday, August 22, 2005
Message Driver Bean with Spring
ActiveMQ เป็น Opensource JMS provider ตัวหนึ่งทีี่มีคนนิยมใช้
เช่น Geronimo ก็เอาไป integrate เป็น Message Service ของตัวเอง
ใน ActiveMQ มี sub project ตัวหนึ่งที่ชื่อ Jencks
ทำหน้าที่เป็น lightweight JCA container
สามารถ config เข้าไปใน Spring Framework เพื่อที่จะให้บริการ Message Driver Bean ได้
(โดยไม่ต้องมี J2ee Application Server)
ทดลองเขียน Message Bean ง่ายๆ
จากนั้นก็ add Container เข้าไปใน Spring
จากนั้นก็ทดลองเขียน Java Class ที่ใช้ในการ start service
เราจะใช้ Spring JmsTemplate เข้ามาช่วย
โดยเขียน Spring Config file ดังนี้
ทดลองส่ง Message ดังนี้
Note: ก่อนจะ run อย่าลืม start ActiveMq ให้เรียบร้อยก่อน
เท่าที่ทดสอบดู ก็ work ok ดี แต่ไม่ค่อยชอบอยู่จุดหนึ่ง ตรง dependency file
ที่ต้องใช้ ลองดูว่าต้องใช้อะไรบ้าง
* connector-api.jar -> JCA api
* geronimo-transaction.jar
* geronimo-connector.jar
* geronimo-kernel.jar
* geronimo-j2ee.jar
* jencks.jar
* activemq-ra.jar
* activemq-core.jar
* geronimo-spec-jms.jar -> JMS api
* geronimo-spec-j2ee-management.jar (javax.management.j2ee)
* concurrent-1.3.4.jar
* commons-logging.jar
กลุ่มล่างๆไม่ติดใจอะไร เพราะเป็น Spec Api ที่ต้อง import อยู่แล้ว
แต่กลุ่มบน จะเห็นว่า dependency กับ Geronimo เยอะมาก
ที่สนใจ JMS ตอนนี้ก็เพราะว่า สามารถเอามาใช้เป็น infrastructure ใน
การ integrate rails, java เข้าหากันได้ (ผ่านทาง TTMP protocol ของ ActiveMQ)
โดยให้ rails เป็นส่วน Web Application และใช้ Java เป็น Business Tier
เช่น Geronimo ก็เอาไป integrate เป็น Message Service ของตัวเอง
ใน ActiveMQ มี sub project ตัวหนึ่งที่ชื่อ Jencks
ทำหน้าที่เป็น lightweight JCA container
สามารถ config เข้าไปใน Spring Framework เพื่อที่จะให้บริการ Message Driver Bean ได้
(โดยไม่ต้องมี J2ee Application Server)
Message Bean
ทดลองเขียน Message Bean ง่ายๆ
package mx.test.mq;
import java.util.Enumeration;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
public class EchoBean implements MessageListener {
public void onMessage(Message msg) {
try {
TextMessage txt = (TextMessage) msg;
System.out.println(txt.getText());
} catch (JMSException je) {
je.printStackTrace();
}
}
}
จากนั้นก็ add Container เข้าไปใน Spring
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org
/dtd/spring-beans.dtd">
<beans>
<bean id="jencks" class="org.jencks.JCAContainer">
<property name="bootstrapContext">
<bean class="org.jencks.factory.BootstrapContextFactoryBean">
<property name="threadPoolSize" value="25"/>
</bean>
</property>
<property name="resourceAdapter">
<bean id="activeMQResourceAdapter" class="org.activemq.ra.ActiveMQRe
sourceAdapter">
<property name="serverUrl" value="tcp://localhost:61616"/>
</bean>
</property>
</bean>
<bean id="monitor" factory-method="addConnector" factory-bean="jencks" singl
eton="true">
<property name="activationSpec">
<bean class="org.activemq.ra.ActiveMQActivationSpec">
<property name="destination" value="Monitor.Queue"/>
<property name="destinationType" value="javax.jms.Queue"/>
</bean>
</property>
<property name="ref" value="echoBean"/>
</bean>
<bean id="echoBean" class="mx.test.mq.EchoBean" singleton="true"/>
</beans>
จากนั้นก็ทดลองเขียน Java Class ที่ใช้ในการ start service
package mx.test.mq;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
ClassPathXmlApplicationContext appContext =
new ClassPathXmlApplicationContext(
new String[] {"beans.xml"});
}
}
ทดลองเขียน Client เพื่อส่ง Message
เราจะใช้ Spring JmsTemplate เข้ามาช่วย
โดยเขียน Spring Config file ดังนี้
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org
/dtd/spring-beans.dtd">
<beans>
<bean id="jmsFactory" class="org.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>tcp://localhost:61616</value>
</property>
</bean>
<!-- Spring JMS Template -->
<bean id="jms" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<ref local="jmsFactory"/>
</property>
<property name="defaultDestinationName" value="Monitor.Queue"/>
</bean>
</beans>
ทดลองส่ง Message ดังนี้
package mx.test.mq;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationC
ontext(
new String[] { "client.xml" });
JmsTemplate jms = (JmsTemplate) appContext.getBean("jms");
jms.send(new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("hello queue world");
}
});
}
}
Note: ก่อนจะ run อย่าลืม start ActiveMq ให้เรียบร้อยก่อน
เท่าที่ทดสอบดู ก็ work ok ดี แต่ไม่ค่อยชอบอยู่จุดหนึ่ง ตรง dependency file
ที่ต้องใช้ ลองดูว่าต้องใช้อะไรบ้าง
* connector-api.jar -> JCA api
* geronimo-transaction.jar
* geronimo-connector.jar
* geronimo-kernel.jar
* geronimo-j2ee.jar
* jencks.jar
* activemq-ra.jar
* activemq-core.jar
* geronimo-spec-jms.jar -> JMS api
* geronimo-spec-j2ee-management.jar (javax.management.j2ee)
* concurrent-1.3.4.jar
* commons-logging.jar
กลุ่มล่างๆไม่ติดใจอะไร เพราะเป็น Spec Api ที่ต้อง import อยู่แล้ว
แต่กลุ่มบน จะเห็นว่า dependency กับ Geronimo เยอะมาก
ที่สนใจ JMS ตอนนี้ก็เพราะว่า สามารถเอามาใช้เป็น infrastructure ใน
การ integrate rails, java เข้าหากันได้ (ผ่านทาง TTMP protocol ของ ActiveMQ)
โดยให้ rails เป็นส่วน Web Application และใช้ Java เป็น Business Tier
Related link from Roti
Java Preferences
เป็น feature หนึ่งใน JDK1.4+ ที่อนุญาติให้เราเก็บ Application Preferences ของเราได้
โดยแต่ละ Platform ก็จะมีวิธีเก็บที่ต่างกัน
เราสามารถเลือก Preferences Store ได้ 2 แบบคือ
แต่ละแบบจะมีที่เก็บต่างกันไป
อย่างเช่นใน Unix
ที่เก็บของ Per User ก็จะอยู่ที่
แต่ถ้าเป็น Per System ก็ขึ้นอยู่กับว่า ถ้าที่ /etc มี directory .java
ก็จะไปสร้างที่ /etc/.java/.systemPrefs
แต่ถ้าไม่มี directory นั้น ก็จะไปใช้ ที่ /JAVA_HOME/.systemPrefs แทน
Note: ที่เก็บกรณี unix นี้สามารถ override ได้โดยใช้ System Properties
(ระวังด้วยว่า ถ้าเป็น IBM JDK หรือ Rockit JDK อาจจะไม่ใช่ค่านี้ก็ได้)
ส่วนใน OS X
ถ้าเป็น per User ก็จะเก็บใน
ถ้าเป็น per System ก็จะอยู่ใน
โดยจะประกอบด้วย 2 file ก็คือ file com.apple.java.util.prefs.plist
จะใช้เก็บ node path อย่างเดียว
แล้วก็ file plist ที่ใช้เก็บค่าจริงๆ อีก 1 file ต่อ 1 node (เฉพาะ leaf node)
การเก็บค่าลง Preferences สามารถใช้ Datatype ได้ดังนี้
กรณีที่ใช้ String หรือ ByteArray ต้องระวังข้อจำกัดในเรื่อง
ขนาดของ value ที่เก็บ โดยค่า maximum length ที่ยอมให้เก็บได้
ก็คือ 8192 characters
ส่วนข้อจำกัดอื่นๆ ก็คือ
ขนาดของ key ก็คือ ความยาวห้ามเกิน 80 characters
ขนาดของ node name ห้ามยาวเกิน 80 characters
Feature ที่น่าสนใจอีกอย่าง ก็คือ สามารถสั่ง Import และ
Export ข้อมูลใน preferences ได้
นอกจากนี้เราสามารถ Implements Preferences ของเราเองได้
โดย implements Interfaces PreferencesFactory
และบอกให้ vm รู้โดยการ set System Property
อ่านเพิ่มเติม
โดยแต่ละ Platform ก็จะมีวิธีเก็บที่ต่างกัน
- Windows เก็บลง Windows Registry
- Unix เก็บลง File System
- Mac Os X เก็บลง plist file ใน Library Folder
เราสามารถเลือก Preferences Store ได้ 2 แบบคือ
- Per User
- Per System
แต่ละแบบจะมีที่เก็บต่างกันไป
อย่างเช่นใน Unix
ที่เก็บของ Per User ก็จะอยู่ที่
~/.java/.userPrefsแต่ถ้าเป็น Per System ก็ขึ้นอยู่กับว่า ถ้าที่ /etc มี directory .java
ก็จะไปสร้างที่ /etc/.java/.systemPrefs
แต่ถ้าไม่มี directory นั้น ก็จะไปใช้ ที่ /JAVA_HOME/.systemPrefs แทน
Note: ที่เก็บกรณี unix นี้สามารถ override ได้โดยใช้ System Properties
- java.util.prefs.userRoot กำหนด root directory ของ user (default คือ user.home)
- java.util.prefs.systemRoot กำหนด root directory ของ system
(ระวังด้วยว่า ถ้าเป็น IBM JDK หรือ Rockit JDK อาจจะไม่ใช่ค่านี้ก็ได้)
ส่วนใน OS X
ถ้าเป็น per User ก็จะเก็บใน
~/Library/Preferencesถ้าเป็น per System ก็จะอยู่ใน
/Library/Preferencesโดยจะประกอบด้วย 2 file ก็คือ file com.apple.java.util.prefs.plist
จะใช้เก็บ node path อย่างเดียว
แล้วก็ file plist ที่ใช้เก็บค่าจริงๆ อีก 1 file ต่อ 1 node (เฉพาะ leaf node)
การเก็บค่าลง Preferences สามารถใช้ Datatype ได้ดังนี้
- String
- boolean
- ByteArray
- double
- float
- int
- long
กรณีที่ใช้ String หรือ ByteArray ต้องระวังข้อจำกัดในเรื่อง
ขนาดของ value ที่เก็บ โดยค่า maximum length ที่ยอมให้เก็บได้
ก็คือ 8192 characters
ส่วนข้อจำกัดอื่นๆ ก็คือ
ขนาดของ key ก็คือ ความยาวห้ามเกิน 80 characters
ขนาดของ node name ห้ามยาวเกิน 80 characters
Feature ที่น่าสนใจอีกอย่าง ก็คือ สามารถสั่ง Import และ
Export ข้อมูลใน preferences ได้
นอกจากนี้เราสามารถ Implements Preferences ของเราเองได้
โดย implements Interfaces PreferencesFactory
และบอกให้ vm รู้โดยการ set System Property
-Djava.util.prefs.PreferencesFactory=my.PreferencesFactoryอ่านเพิ่มเติม
Related link from Roti
Sunday, August 21, 2005
Javascript 101
อ่านเจอ Idea ของ 'bact ในเรื่องการใช้ closure ในการ modify tree structure แล้ว
ก็เลยไปค้นหาดูบทความดีๆ ที่พูดเรื่อง closure พบบทความที่น่าสนใจก็คือ
Javascript Closures
หลังจากนั่นอ่่าน(ด้วยความทรมาน) อยู่พักใหญ่ ก็เลยเกิดแรงบันดาลใจว่า
เห็นควรจะได้ฤกษ์ทำความเข้าใจในเรื่อง Javascript ของตัวเองเสียที (หลีกเลี่ยงมานานแล้ว)
Note: ข้อควรระวัง. Javascript นี้อ้างอิง ECMAScript เป็นหลัก
ไม่ได้ดูเรื่อง browser compatiblilty เลย
Note: คำอธิบายนี้เป็น abstract
การ implement ของ Javascript Engine จริงๆ อาจจะมี object model
ซับซ้อนกว่านี้
Execution Context ก็คือ Object ธรรมดานี่แหล่ะ
เมื่อเราเริ่มต้น run javascript
เจ้า javascript engine ก็จะทำการ initialize "Global Execution Context"
ให้เรา โดยมี method (หรือ function) จำนวนหนึ่งมาให้ เช่น Date, encodeURI, isNAN, ...
(พวกนี้ก็คือ build in function ของ javascript นั่นเอง)
เมื่อไรก็ตามที่เรามีการ call function
เจ้า Javascript engine ก็จะทำการสร้าง Execution Context Object
ตัวใหม่ให้เรา และทำการ push current Execution Context ลงใน
Stack ก่อน เพื่อที่ว่าเมื่อมีการ return ออกจาก function แล้ว
ก็จะ pop เอา Execution Context ที่อยู่ใน Stack ขึ้นมาเป็น current Execution Context ต่อไป
ลองดูตัวอย่าง
เมื่อเริ่มต้น run Javascript นี้.
เจ้า Javascript Engine ก็จะทำการ
Initialize new Global Execution Context ขึ้นมา.
เมื่อ Engine อ่านเจอบรรทัดที่ 1 ที่เป็นการประกาศ function
ก็จะทำการ add function นี้เข้าเป็นสมาชิก (method)
ของ Global Execution Context
ส่วนที่บรรทัดที่ 5 ที่มีการ initialize variable.
variable
current scope ซึ่งก็คือ Global Execution Context นั่นเอง
ที่บรรทัดที่ 6 มีการ call ไปที่ function
JavaScript Engine ก็จะสร้าง Execution Context ใหม่ขึ้นมา,
จากนั้นก็ push Global Execution Context ลง stack ก่อน
สุดท้ายก็ set Execution Context ตัวใหม่นี้ เป็น current Execution Context
ที่บรรทัดที่ 2 มีการ initialize variable
variable
Execution Context และเมื่อจบจาก function
current Execution Context ก็จะถูก Garbage ทิ้งไป (ตัวแปร x
ก็จะหายไป) และทำการ pop Execution Context จาก stack
ขึ้นมา set เป็น current Execution Context แทน
ตัวนี้เป็น internal Object Reference ซึ่งชี้ไปยัง Object ที่เราต้องการให้เป็นต้นแบบ
(ทุก Object ภายใน javascript จะมี property __proto__ เสมอ)
ดูตัวอย่างดีกว่า เข้าใจง่ายกว่า
ที่บรรทัดที่ 7 เรากำหนดให้
ของ Apple Function ไห้ชี้ไปยัง Object ที่สร้างขึ้นมาจาก Object Fruit
เมื่อไรก็ตามที่เราเรียกมีการ
Instance ที่สร้างขึ้นใหม่นี้จะถูก set ค่า __proto__ ให้ชี้ไปยัง
ค่าที่กำหนดไว้ใน
ที่นี้คำถาม ก็คือ ทำไมต้องมี __proto__
__proto__ จะถูกใช้เมื่อเรามีการ access properties ของ Object นั้น
แล้ว JavaScript Engine ไม่สามารถหา property ชื่อที่เราต้องการใน Object นั้นได้
เจ้า Javascript engine ก็จะขยับออกไปหา property นั้นจาก
Object ที่ __proto__ ชี้อยู่แทน ถ้าหาไม่เจออีก ก็ขยับขึ้นไปอีก
จนกว่า จะเจอค่า __proto__ ที่เป็น null
จากข้างบนตัวอย่างข้างบน เราสามารถเขียนแบบกำหนด
ได้ตรงๆดังนี้
จากตัวอย่างนี้
การที่เราประกาศแบบนี้ ทุกครั้งที่เรา new Position ขึ้นมา
จะเกิด Object dump ขึ้นมาด้วย ถ้ามี Position 100 instance
ก็จะมี Object dump 100 instance ด้วยเช่นกัน
ดังนั้นเป็นการดีกว่าที่จะกำหนดเป็น prototype แทนเพื่อให้ลดการใช้ memory ลง
ปัญหาอีกอย่างของ inner function ก็คือ
memory leak
การที่เรากำหนด function add ไว้อย่างนี้
ทำให้เกิดปัญหา memory leak ได้
เนื่องจากในขณะที่เรา new function innerAdd
ขึ้นมานั้น innerAdd จะมี reference ไปยัง
กลุ่มของ object ที่อยู่ใน execution context จำนวนหนึ่ง
และเมื่อมีการ return ค่ากลับออกมา
Garbage ก็จะพยายาม release resources
ที่ถูกจองใน Execution Context ของ funcion add
แต่ก็ไม่สามารถ release ได้ (เพราะยังมี pointer จาก a อ้างถึงอยู่)
วิธีที่ดีกว่า ก็คือ
ก็เลยไปค้นหาดูบทความดีๆ ที่พูดเรื่อง closure พบบทความที่น่าสนใจก็คือ
Javascript Closures
หลังจากนั่นอ่่าน(ด้วยความทรมาน) อยู่พักใหญ่ ก็เลยเกิดแรงบันดาลใจว่า
เห็นควรจะได้ฤกษ์ทำความเข้าใจในเรื่อง Javascript ของตัวเองเสียที (หลีกเลี่ยงมานานแล้ว)
Note: ข้อควรระวัง. Javascript นี้อ้างอิง ECMAScript เป็นหลัก
ไม่ได้ดูเรื่อง browser compatiblilty เลย
Execution Context
Note: คำอธิบายนี้เป็น abstract
การ implement ของ Javascript Engine จริงๆ อาจจะมี object model
ซับซ้อนกว่านี้
Execution Context ก็คือ Object ธรรมดานี่แหล่ะ
เมื่อเราเริ่มต้น run javascript
เจ้า javascript engine ก็จะทำการ initialize "Global Execution Context"
ให้เรา โดยมี method (หรือ function) จำนวนหนึ่งมาให้ เช่น Date, encodeURI, isNAN, ...
(พวกนี้ก็คือ build in function ของ javascript นั่นเอง)
เมื่อไรก็ตามที่เรามีการ call function
เจ้า Javascript engine ก็จะทำการสร้าง Execution Context Object
ตัวใหม่ให้เรา และทำการ push current Execution Context ลงใน
Stack ก่อน เพื่อที่ว่าเมื่อมีการ return ออกจาก function แล้ว
ก็จะ pop เอา Execution Context ที่อยู่ใน Stack ขึ้นมาเป็น current Execution Context ต่อไป
ลองดูตัวอย่าง
1: function callMe() {
2: var x = 10;
3: }
4:
5: a = 'hi';
6: callMe();
เมื่อเริ่มต้น run Javascript นี้.
เจ้า Javascript Engine ก็จะทำการ
Initialize new Global Execution Context ขึ้นมา.
เมื่อ Engine อ่านเจอบรรทัดที่ 1 ที่เป็นการประกาศ function
ก็จะทำการ add function นี้เข้าเป็นสมาชิก (method)
ของ Global Execution Context
ส่วนที่บรรทัดที่ 5 ที่มีการ initialize variable.
variable
a ตัวนี้จะถูกสร้างเป็นสมาชิก (property) ของcurrent scope ซึ่งก็คือ Global Execution Context นั่นเอง
ที่บรรทัดที่ 6 มีการ call ไปที่ function
callMeJavaScript Engine ก็จะสร้าง Execution Context ใหม่ขึ้นมา,
จากนั้นก็ push Global Execution Context ลง stack ก่อน
สุดท้ายก็ set Execution Context ตัวใหม่นี้ เป็น current Execution Context
ที่บรรทัดที่ 2 มีการ initialize variable
xvariable
x จะถูกสร้างให้เป็นสมาชิกของ currentExecution Context และเมื่อจบจาก function
callMecurrent Execution Context ก็จะถูก Garbage ทิ้งไป (ตัวแปร x
ก็จะหายไป) และทำการ pop Execution Context จาก stack
ขึ้นมา set เป็น current Execution Context แทน
__proto__
ตัวนี้เป็น internal Object Reference ซึ่งชี้ไปยัง Object ที่เราต้องการให้เป็นต้นแบบ
(ทุก Object ภายใน javascript จะมี property __proto__ เสมอ)
ดูตัวอย่างดีกว่า เข้าใจง่ายกว่า
1 : function Fruit() {
2 : this.eatable = true;
3 : }
4 : function Apple() {
5 : this.color = "red";
6 : }
7 : Apple.prototype = new Fruit();
8 : var apple1 = new Apple();
9 : print (apple1.color); // ==> red
10: print (apple1.eatable); // ==> true
11: print (apple1.x);
ที่บรรทัดที่ 7 เรากำหนดให้
prototype propertyของ Apple Function ไห้ชี้ไปยัง Object ที่สร้างขึ้นมาจาก Object Fruit
เมื่อไรก็ตามที่เราเรียกมีการ
new Apple() Instance ที่สร้างขึ้นใหม่นี้จะถูก set ค่า __proto__ ให้ชี้ไปยัง
ค่าที่กำหนดไว้ใน
prototype propertyที่นี้คำถาม ก็คือ ทำไมต้องมี __proto__
__proto__ จะถูกใช้เมื่อเรามีการ access properties ของ Object นั้น
แล้ว JavaScript Engine ไม่สามารถหา property ชื่อที่เราต้องการใน Object นั้นได้
เจ้า Javascript engine ก็จะขยับออกไปหา property นั้นจาก
Object ที่ __proto__ ชี้อยู่แทน ถ้าหาไม่เจออีก ก็ขยับขึ้นไปอีก
จนกว่า จะเจอค่า __proto__ ที่เป็น null
จากข้างบนตัวอย่างข้างบน เราสามารถเขียนแบบกำหนด
__proto__ได้ตรงๆดังนี้
function Fruit() {
this.eatable = true;
}
function Apple() {
this.color = 'red';
}
y = new Apple();
z = new Fruit();
y.__proto__ = z;
print (y.color);
print (y.eatable);
ข้อควรระวังในการใช้ Inner Function
จากตัวอย่างนี้
function Position(x, y) {
this.dump = function() {
println ("x = " + x + ", y = " + y);
}
}
p = new Position(10, 20);
p.dump();
การที่เราประกาศแบบนี้ ทุกครั้งที่เรา new Position ขึ้นมา
จะเกิด Object dump ขึ้นมาด้วย ถ้ามี Position 100 instance
ก็จะมี Object dump 100 instance ด้วยเช่นกัน
ดังนั้นเป็นการดีกว่าที่จะกำหนดเป็น prototype แทนเพื่อให้ลดการใช้ memory ลง
function Position(x, y) {
this.x = x;
this.y = y;
}
Position.prototype.dump = function () {
println ("x = " + this.x + ", y = " + this.y);
}
p = new Position(10, 20);
p.dump();
ปัญหาอีกอย่างของ inner function ก็คือ
memory leak
function add(x) {
return function innerAdd(y) {
return x + y;
}
}
a = add(10);
println (a(2)); // ==> 12
การที่เรากำหนด function add ไว้อย่างนี้
ทำให้เกิดปัญหา memory leak ได้
เนื่องจากในขณะที่เรา new function innerAdd
ขึ้นมานั้น innerAdd จะมี reference ไปยัง
กลุ่มของ object ที่อยู่ใน execution context จำนวนหนึ่ง
และเมื่อมีการ return ค่ากลับออกมา
Garbage ก็จะพยายาม release resources
ที่ถูกจองใน Execution Context ของ funcion add
แต่ก็ไม่สามารถ release ได้ (เพราะยังมี pointer จาก a อ้างถึงอยู่)
วิธีที่ดีกว่า ก็คือ
function add(x) {
this.x = x;
return addAgain;
}
function addAgain(y) {
return this.x + y;
}
a = add(10);
println (a(2)); // ==> 12
Related link from Roti
Friday, August 19, 2005
ใช้ Spring AOP ในการ initialize Hibernate Lazy Properties
ปัญหาของการใช้ Hibernate ตัวหนึ่งที่เชื่อว่าทุกคนคงจะเคยเจอ (โดยเฉพาะพวกที่
พัฒนา Web Application) ก็คือ ประเด็นเรื่อง LazyInitializationException
ซึ่งเกิดจากการพยายาม access Lazy Properties หลังจากที่ได้มีการ
close Session ไปแล้ว
สภาพปัญหา
สมมติว่าเราออกแบบ Class Customer กับ Class CustomerHistory
โดย CustomerHistory ต้องการจะไว้ใช้เก็บประวัติการซื้อของลูกค้า

เวลาเรา Mapping เข้ากับ Hibernate
Navigation จาก customer ไปยัง CustomerHistory จะ config ได้ดังนี้
จะเห็นว่าใน code ตัวอย่างนี้มี
เมื่อไรก็ตามที่สั่ง query Customer นี้เช่น
Note:สำหรับผู้ที่สงสัยว่าทำไมไม่เขียน session.find("from Customer...")
เพราะว่าใน Hibernate3 Class Session ไม่มี method find ให้ใช้แล้ว
Hibernate ก็จะจัดการ query โดยการ
ซึ่งจะเห็นว่าถ้าเอาไปใช้งานในหน้าจอประเภทสอบถามที่ดึง customer ทีเดียวหลายๆคน
หรือหน้าจอรายงาน ก็จะเกิดปัญหาในด้าน Performance Issue แน่นอน
ดังนั้นเรามักจะกำหนดให้
ซึ่งเมื่อกำหนดเช่นนี้แล้ว Hibernate จะยังไม่ select ข้อมูลจาก customer_history
จนกว่าจะมีการ access จริงเกิดขึ้น (เช่นมีการเรียกใช้ customer.getCustomerHistory().size())
ซึ่งการ set
LazyInitializeException
ดังนั้นในกรณีที่เรารู้ว่า Object เราจะถูกใช้หลังจากที่ Session close แล้ว
และมีการ access lazy Properties ด้วย. Hibernate เปิดโอกาสให้เรา
initialize Lazy Property ด้วยคำสั่ง
ในแง่การปฎิบัติแล้ว วิธี Hibernate.initialize ก็สามารถใช้การได้ดี
แต่ในแง่ความสวยงามของ Design แล้วดูมันผิดที่ิผิดทางอยู่
สุดท้ายพวกคนที่ใช้ Spring ก็เลยมี Idea ที่จะแก้ไขปัญหานี้ด้วยการใช้
Spring AOP เข้ามาช่วย (เพื่อให้การกำหนด initialize property
มีลักษณะเป็น Declarative มากขึ้น)
ลองมาดูวิธีการที่เขาใช้
เริ่มจากสถานะการณ์ปกติที่เราใช้ Spring ก่อน
define Service Layer ด้วย Interface
ใน config file เรา declare CustomerService โดยใช้ Proxy
เพื่อจะได้ใส่ hibernate interceptor ที่ทำหน้าที่ open, close Session
ให้เราอัตโนมัติ
เมื่อไรก็ตามที่เราต้องการ Initialize Properties เราก็สามารถ
แทรก Interceptor เข้าไปได้ดังนี้
และทำการแทรก Interceptor เข้าไปใน proxy ดังนี้
Class TouchingNameMatchMethodAdvisor จะคอยดักว่ามีการ เรียกใช้ method
ที่กำหนดหรือไม่ ถ้ามีการเรียกใช้ ก็จะใช้ OGNL Expression ที่เรากำหนด
เข้าไป access property
สรุป
ในแง่ Design การยกเอา Initilize code ออกมาเป็น Declarative ดู
มี style กว่า แต่ในแง่ปฏิบัติและความยุ่งยากแล้ว อาจจะไม่คุ้มกันก็ได้
เพราะถ้าเขียนเป็น code แล้วจะมี code แค่เพียง 1 บรรทัด แต่ถ้ายกออกมา
เป็น declarative แล้วต้องเขียน code ขึ้นอีก 12 บรรทัด
Note: Class TouchingNameMatchMethodAdvisor อยู่ใน Project SpringModules
และยังไม่รวมอยู่ใน release build ของ SpringModules
อ่านเพิ่มเติม
พัฒนา Web Application) ก็คือ ประเด็นเรื่อง LazyInitializationException
ซึ่งเกิดจากการพยายาม access Lazy Properties หลังจากที่ได้มีการ
close Session ไปแล้ว
สภาพปัญหา
สมมติว่าเราออกแบบ Class Customer กับ Class CustomerHistory
โดย CustomerHistory ต้องการจะไว้ใช้เก็บประวัติการซื้อของลูกค้า

เวลาเรา Mapping เข้ากับ Hibernate
Navigation จาก customer ไปยัง CustomerHistory จะ config ได้ดังนี้
<set
name="customerHistory"
lazy="false"
cascade="none"
sort="unsorted"
order-by="invoiceDate"
>
<key
column="customer_id"
>
</key>
<one-to-many
class="domain.CustomerHistory"
/>
</set>
จะเห็นว่าใน code ตัวอย่างนี้มี
lazy="false"เมื่อไรก็ตามที่สั่ง query Customer นี้เช่น
List results = session.createQuery("from Customer as c").list();
Note:สำหรับผู้ที่สงสัยว่าทำไมไม่เขียน session.find("from Customer...")
เพราะว่าใน Hibernate3 Class Session ไม่มี method find ให้ใช้แล้ว
Hibernate ก็จะจัดการ query โดยการ
select * from Customer
จากนั้น แต่ละ Customer ที่ได้มา
select * from customer_history
ซึ่งจะเห็นว่าถ้าเอาไปใช้งานในหน้าจอประเภทสอบถามที่ดึง customer ทีเดียวหลายๆคน
หรือหน้าจอรายงาน ก็จะเกิดปัญหาในด้าน Performance Issue แน่นอน
ดังนั้นเรามักจะกำหนดให้
lazy="true" เสียมากกว่าซึ่งเมื่อกำหนดเช่นนี้แล้ว Hibernate จะยังไม่ select ข้อมูลจาก customer_history
จนกว่าจะมีการ access จริงเกิดขึ้น (เช่นมีการเรียกใช้ customer.getCustomerHistory().size())
ซึ่งการ set
lazy="true" นี่แหละที่เป็นที่มาของปัญหาLazyInitializeException
ดังนั้นในกรณีที่เรารู้ว่า Object เราจะถูกใช้หลังจากที่ Session close แล้ว
และมีการ access lazy Properties ด้วย. Hibernate เปิดโอกาสให้เรา
initialize Lazy Property ด้วยคำสั่ง
Hibernate.initialize(..)ในแง่การปฎิบัติแล้ว วิธี Hibernate.initialize ก็สามารถใช้การได้ดี
แต่ในแง่ความสวยงามของ Design แล้วดูมันผิดที่ิผิดทางอยู่
สุดท้ายพวกคนที่ใช้ Spring ก็เลยมี Idea ที่จะแก้ไขปัญหานี้ด้วยการใช้
Spring AOP เข้ามาช่วย (เพื่อให้การกำหนด initialize property
มีลักษณะเป็น Declarative มากขึ้น)
ลองมาดูวิธีการที่เขาใช้
เริ่มจากสถานะการณ์ปกติที่เราใช้ Spring ก่อน
define Service Layer ด้วย Interface
public interface ICustomerServices {
public List findAllCustomer();
}
ใน config file เรา declare CustomerService โดยใช้ Proxy
เพื่อจะได้ใส่ hibernate interceptor ที่ทำหน้าที่ open, close Session
ให้เราอัตโนมัติ
<bean id="myHibernateInterceptor"
class="org.springframework.orm.hibernate3.HibernateInterceptor">
<property name="sessionFactory">
<ref bean="mySessionFactory"/>
</property>
</bean>
<bean id="customerServiceTarget" class="service.CustomerService">
<property name="sessionFactory">
<ref bean="mySessionFactory"/>
</property>
</bean>
<bean id="customerService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>service.ICustomerServices</value>
</property>
<property name="interceptorNames">
<list>
<value>myHibernateInterceptor</value>
<value>customerServiceTarget</value>
</list>
</property>
</bean>
เมื่อไรก็ตามที่เราต้องการ Initialize Properties เราก็สามารถ
แทรก Interceptor เข้าไปได้ดังนี้
<bean id="cust-service-findall_initialize"
class="org.springmodules.aop.framework.TouchingNameMatchMethodAdvisor">
<property name="mappedNames">
<value>findAllCustomer</value>
</property>
<property name="advice.ognl">
<list>
<value>#returned.{customerHistory.size}</value>
</list>
</property>
</bean>
และทำการแทรก Interceptor เข้าไปใน proxy ดังนี้
<bean id="customerService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>service.ICustomerServices</value>
</property>
<property name="interceptorNames">
<list>
<value>myHibernateInterceptor</value>
<value>cust-service-findall_initialize</value>
<value>customerServiceTarget</value>
</list>
</property>
</bean>
Class TouchingNameMatchMethodAdvisor จะคอยดักว่ามีการ เรียกใช้ method
ที่กำหนดหรือไม่ ถ้ามีการเรียกใช้ ก็จะใช้ OGNL Expression ที่เรากำหนด
เข้าไป access property
สรุป
ในแง่ Design การยกเอา Initilize code ออกมาเป็น Declarative ดู
มี style กว่า แต่ในแง่ปฏิบัติและความยุ่งยากแล้ว อาจจะไม่คุ้มกันก็ได้
เพราะถ้าเขียนเป็น code แล้วจะมี code แค่เพียง 1 บรรทัด แต่ถ้ายกออกมา
เป็น declarative แล้วต้องเขียน code ขึ้นอีก 12 บรรทัด
Note: Class TouchingNameMatchMethodAdvisor อยู่ใน Project SpringModules
และยังไม่รวมอยู่ใน release build ของ SpringModules
อ่านเพิ่มเติม
Related link from Roti
Subscribe to:
Posts (Atom)