LAB 11 : แข่งม้ากันไหม?
วันนี้นิสิตจะได้ทำ LAB เกี่ยวกับ Thread วันนี้จะมีเนื้อหาค่อนข้างเยอะ
ทำใจให้สบายแล้วค่อยๆ ทำไป การเขียนโปรแกรมที่มีหลาย Thread นั้นค่อนข้างใช้จินตนาการนิดนึง
เพราะจะแตกต่างกับการเขียนโปรแกรมที่มี Thread เดียว
1. แข่งม้ากันไหม?
คำเตือน : โจทย์ข้อแรกห้ามเอาไปดัดแปลงใช้ในการพนัน
และการพนันเป็นสิ่งผิดกฎหมาย
ปัญหา
:
ในวันศึกประลองม้าแข่งประจำปีที่ประเทศสารขันธ์
มีม้าเต็งอยู่ 3
ตัวด้วยกัน พลัดกันแพ้พลัดกันชนะกันเป็นประจำ
โจทย์ประลองทุกปีคือวิ่งแข่งเป็นระยะทาง 1,000 m ใครเข้าเส้นชัยก่อนเป็นผู้ชนะ
ปัจจัยที่ทำให้เป็นผู้ชนะนั้น ประกอบไปด้วย 2 สิ่งด้วยกัน
1.) ความสามารถของม้า -
ม้าแต่ละตัวมีความเร็วไม่เท่ากัน
และความแข็งแกร่งในการวิ่งระยะทางไม่เท่ากัน
2.) ความสามารถของ Jockey
ที่บังคับม้า - Jockey แต่ละคนมีประสบการณ์ในการบังคับม้าต่างกันไป
นิสิตได้รับมอบหมายจากกรรมการให้ช่วยสร้างโปรแกรม
ที่ช่วยให้กรรมการสามารถปล่อยม้าออกจากจุดเริ่มต้น สามารถสั่งหยุด/ไปต่อ ม้าตัวใดตัวนึง หรือ หยุด/ไปต่อ
ม้าทุกตัวขณะวิ่งได้ สามารถสั่งยกเลิกผลการแข่งขันได้
ตัวอย่าง
ผลลัพธ์ที่คาดว่าจะได้
<<พี่ไม่เน้นเรื่องหน้าตา ทำออกมาให้ดูได้ ไม่ต้องสวยมากก็ได้>>
ให้นิสิตดาวน์โหลด
TA-HorseRacer.jar และลองเล่นดู executable
jar นี้เป็นตัวอย่างที่พี่ TA ได้ทำสำเร็จแล้ว
และท้าทายให้นิสิต ลองสร้างเลียนแบบดู
เพื่อไม่ให้ยากเกินไป
พี่ได้ขึ้นโครงของโปรเจกต์ไว้ให้แล้ว ให้น้องดาวน์โหลด HorseRaceForStudent.zip มาเพื่อใช้เป็นแนวทาง
องค์ความรู้ที่จำเป็นต้องมี
- Thread วิธีการสร้าง Thread
ผ่าน Interface Runnable
- Swing Component ที่ใช้ในการเป็นตัวแทนการวิ่งของม้า
นั่นคือ JProgressBar
- การใช้เทคนิก sleep
ใน Thread และวิธีการ pause/continue
Thread ในแบบต่างๆ
- ความรู้เรื่อง Thread
Priority
เรื่องแต่ละเรื่องนั้นพี่
TA
ได้สร้างเอกสารอธิบายวิธีการทำงานคร่าวๆ ไว้ให้แล้ว
ถ้านิสิตมีความรู้เรื่องนั้นอยู่แล้ว ก็สามารถข้ามไปอ่านส่วนนี้ไปเลยก็ได้
ความต้องการของระบบ
มีม้าเต็งอยู่
3
ทีมด้วยกัน โดยมีชื่อดังนี้
1. มงคลอัศดร
2. ขวัญใจรัศมี
3. อาชาลมกรด
ให้นิสิต extends
class JProgressBar และ implements Horse Interface เพื่อสร้าง Thread ลู่วิ่งม้าแข่งแต่ละตัว
เนื่องจากข้อกำหนดที่ว่า ม้าแต่ละตัวมีความสามารถไม่เท่ากัน แต่ทุกตัวมีก้าวที่เท่ากัน
นั่นคือ 1 ก้าวได้งาน 1 m ให้นิสิตใช้เทคนิก
Thread Sleep ในการกำหนดความเร็วของม้า
โดยกำหนดให้ม้ามีความเร็วดังนี้
ม้าตัวที่ 1 - มงคลอัศดร มีความเร็ว 220 m/s
ม้าตัวที่ 2 - ขวัญใจรัศมี มีความเร็ว 200 m/s
ม้าตัวที่ 3 - อาชาลมกรด มีความเร็ว 250 m/s
( คำใบ้ : ถ้ากำหนดให้ม้าทุกตัวมีก้าวที่ยาวเท่ากันหมด ม้าต้อง sleep กี่ millisec ถึงจะทำให้ม้าวิ่งได้ความเร็ว 100
m/s พอดี? )
และจากข้อกำหนดอีกเช่นกัน
ว่าด้วยเรื่อง Jockey
แต่ละคนนั้นก็เก่งไม่เท่ากัน ให้นิสิตใช้ความรู้เรื่อง Thread
Priority ให้เป็นประโยชน์ โดยโจทย์กำหนดให้ Jockey ประจำม้าแต่ละตัวมีความเก่งไม่เท่ากันดังนี้
ม้าตัวที่ 1 - มงคลอัศดร มี Jockey ที่เก่งในระดับ ปกติ (5)
ม้าตัวที่ 2 - ขวัญใจรัศมี มี Jockey ที่เก่งในระดับ เก่งมากที่สุด
(10)
ม้าตัวที่ 3 - อาชาลมกรด มี Jockey ที่เก่งในระดับ แย่ที่สุด
(1)
ทุกๆ ก้าวที่ม้าทำ ให้รายงานผลลัพธ์ทาง console ด้วย เช่น "อาชาลมกรด - Minimum 120 m/s
: 989/1000" และเมื่อม้าเข้าเส้นชัยให้รายงานผลว่า "อาชาลมกรด - Minimum 120 m/s, I finish!"
ให้ใช้ System.out.println()
ได้เลย พี่เขียนโค้ดผูก console เข้ากับ JTextArea
ไว้ให้แล้ว
ตัว ProgressBar
กำหนดให้มีค่า maximum เท่ากับความยาวของลู่วิ่งนั่นคือ
1000 m และย้ำอีกครั้ง
กำหนดให้ม้าทุกตัวก้าวได้งานเท่าๆ กัน นั่นคือ 1 m/ก้าว
และเพื่อให้ไม่ให้ยากเกินไป
พี่ TA ได้ช่วยขึ้นโครงสร้าง class HorseProgress, HorsePanelและ HorseRaceFrame มาให้แล้ว ให้นิสิต implement
ส่วนที่เหลือให้สมบูรณ์เหมือนตัวอย่าง พี่ได้ comment กำกับส่วนที่น้องต้องทำเพิ่มไว้ให้แล้ว ถ้าส่วนไหนพี่ไม่ได้พูดอะไรแสดงว่า
ถ้าน้องใช้ตรรกะแนวคิดเดียวกันกับพี่ น้องก็ไม่ต้องทำอะไรเพิ่มเช่นกัน
แต่หากคิดต่างกัน จะเพิ่มหรือลดโค้ดในส่วนนั้นก็ได้
ข้อนี้ถ้าจับจุดได้จะไม่ยากเลย
เพราะใบ้ และเขียน code ให้เกือบสมบูรณ์แล้ว ขอให้น้องๆ
ตั้งใจทำและศึกษาการใช้ Thread ให้ดี
ปุ่มและสถานะที่สัมพันธ์
-
เมื่อกดปุ่ม reset ให้ Thread
Horse นั้นมีตำแหน่งกลับไปที่จุดเริ่มต้น สถานะการแข่งขันเป็นออกตัว
แต่สถานะการเคลื่อนที่เป็น หยุด
-
เมื่อกดปุ่ม start ให้ Thread
Horse นั้นไม่มีผลกับตำแหน่งที่อยู่ มีสถานะการแข่งขันเป็นออกตัว
มีสถานะการเคลื่อนที่เป็น วิ่ง
-
เมื่อกดปุ่ม toggle Play/Pause ให้ Thread
Horse นั้นไม่มีผลกับตำแหน่งที่อยู่ มีสถานะการแข่งขันคงเดิม
มีสถานะการเคลื่อนที่ตรงกันข้ามเดิม
-
เมื่อกดปุ่ม restart all ให้ Thread
Horse ทั้งหมดมีสถานะการแข่งขันเป็นเริ่มต้นใหม่
มีสถานะการเคลื่อนที่เป็นวิ่ง แล้วให้ข้อความใน console ว่างเปล่า
-
เมื่อกดปุ่ม reset all ให้ Thread
Horse ทั้งหมดมีสถานะการแข่งขันเป็นเริ่มต้นใหม่
มีสถานะการเคลื่อนที่เป็นหยุด
-
เมื่อกดปุ่ม toggle Play/Pause all ให้
Thread Horse ทั้งหมดมีสถานะการแข่งขันคงเดิม
มีสถานะการเคลื่อนที่ตรงกันข้าม
องค์ความรู้ประกอบ
เนื่องจาก Lab นี้ใช้เนื้อหาค่อนข้างมาก พี่ TA จึงได้สร้างเอกสารอธิบายเรื่องที่คาดว่านิสิตจะไม่เข้าใจเอาไว้ให้
พร้อมตัวอย่างการใช้งาน นิสิตสามารถดาวน์โหลดได้ที่นี่ Lab10_Example.zip ภายในตัวอย่างจะประกอบไปด้วยโปรแกรมสั้นๆ
ที่ช่วยให้นิสิตเข้าใจและสามารถทำ Lab นี้ได้อย่างลื่นไหลยิ่งขึ้น
นอกจากนี้พี่
TA
ยังได้เตรียมเอกสารอธิบายเรื่อง Thread ที่น้องจำเป็นต้องรู้สำหรับการทำงาน
1.การหยุด Thread
ถ้านิสิตสังเกตจะเห็นว่ามี
method
stop, suspend ใน class Thread ด้วย
แต่ถูกสั่งยกเลิกใช้ไปแล้ว เนื่องจากเหตุผลที่ว่าการใช้ method เหล่านั้นส่งผลให้เกิดความเสียหายได้ เช่นถ้านิสิตเรียก stop() จากภายใน method ที่มีคุณสมบัติ synchronized
อาจส่งผลให้เกิด deadlock ขึ้นภายในโดยไม่รู้ตัว
เปรียบเทียบให้ฟังง่ายๆ เหมือนนิสิตขังตัวเองไว้ในบ้านและไม่สามารถออกมาได้
การหยุด Thread นั้นที่ถูกต้องและเป็นที่นิยมทำได้ 2 วิธีด้วยกัน
1. ใช้ boolean กำกับ
วิธีนี้ boolean ตัวนั้นจะต้องเป็น object member ของ Thread นั้นๆ และต้องมีคุณสมบัติ volatile ด้วย
วิธีนี้ดูได้จากตัวอย่าง
(example2.using.flag)
2. ใช้ inturrupt()
การใช้ boolean กำกับนั้นมีข้อเสียในบางกรณี นั่นคือ ถ้าลองนึกภาพกรณีที่แย่ๆ เช่น หากใน Thread
มีคำสั่ง sleep 5 วินาที และ boolean
flag ถูกกำกับเอาไว้ Thread นี้อย่างน้อยต้องรอให้ครบ
5 วินาทีก่อนถึงจะหยุดได้
ซึ่งในบางความต้องการก็เป็นเรื่องที่รับไม่ได้ อาจจะส่งผลให้ทำงานผิดพลาดได้
การเรียก inturrupt()
นั้นมีข้อดีคือ สามารถหยุด thread ได้ทันที
แม้ Thread อยู่ในสภาพหลับอยู่ หากเกิดกรณีนี้ขึ้น method
sleep จะ Throw InturruptedException ออกมา
เราสามารถเช็คว่า thread มี exception นี้เกิดขึ้นและยังไม่ได้ถูกจัดการ
ได้จาก method isInturrpted() ตัวอย่างการใช้วิธีการหยุดแบบนี้
สามารถดูได้ในตัวอย่าง example2.using.inturrupt
Race Condtion
race condition คือพฤติกรรมที่ไม่พึงประสงค์เกิดขึ้น
สืบเนื่องมาจากมี Thread มากกว่า 1 ตัวพยายามเรียกใช้ข้อมูลเดียวกัน
ผลลัพธ์ที่ออกมาจึงไม่สามารถคาดเดาได้ ขึ้นอยู่กับว่า ณ เวลานั้น Thread เส้นไหนเป็นผู้ชนะและได้ครอบครองข้อมูลก่อนกัน
วิธีการแก้ไข
เราสามารถแก้ไขปัญหา
Race
Condition ได้จากการทำให้ Thread ต่างๆ Synchronize
กัน กล่าวคือ object ใดๆ ต้องล็อคให้ Thread
ใด Thread นึง ทำงานจนเสร็จ แล้วจึงค่อย ปลดล็อคให้
Thread ถัดไปทำงาน
ใน Java เราสามารถกำหนดให้ method ใดๆ เป็น method ที่มีคุณสมบัติ synchronize ได้ผ่านการประกาศ synchronized
ไว้ที่ signature ของ method นั้นๆ ภายใน method นั้น จะมีเพียง Thread เดียวที่มีสิทธิ์ทำงานภายในนั้นได้ พึงระลึกอยู่เสมอว่าการ lock ที่เกิดขึ้น เกิดในระดับ instance ไม่ใช่ระดับ class
การ synchronize
อีกแบบที่ทำได้คือระดับ block นั่นคือใช้ synchronize
block ล้อมรอบข้อมูลในส่วนที่ต้องการจะ lock ให้มี
Thread เดียวที่สามารถ execute ได้
ตัวอย่าง
synchronized( object ) {
}
โดยที่ object คือ instance
ที่มีกรรมสิทธิ์ครอบครอง lock
wait()/notify()
ในบางทีเมื่อมี
Thread
บางตัวมีเงื่อนไขบางอย่างในการที่ตัวมันเองจะทำงานได้ การใช้วิธี wait-and-notify
สามารถช่วยให้ Thread สื่อสารถึงกัน เพื่อส่งมอบหน้าที่การทำงานให้กันและกันได้
ตัวอย่างคลาสิกเรื่องนี้ได้แก่
ตัวอย่าง consumer/producer
เมื่อมีถาดน้ำอัดลมที่บรรจุน้ำอัดลมจนเต็มถาด
การจะเติมน้ำอัดลมลงไปเพิ่มคนขายก็ต้องรอ (wait) เพราะตราบใดที่ยังไม่มีลูกค้ามาซื้อน้ำอัดลมไปก็ไม่สามารถเติมได้
(รอการnotify จากลูกค้า) เช่นเดียวกันหากไม่มีน้ำอัดลมในถาดคนซื้อก็ต้องรอ (wait) ให้คนขายเอาน้ำอัดลมมาเติมจนครบกำหนดจำนวนที่ตัวเองต้องการจึงจะเอาไปได้
จากตัวอย่างที่ยกมานี้ จะเห็นได้ว่า ทั้งคนขาย และคนซื้อต่างก็ทำหน้าที่ wait-and-notify
ให้กันและกัน
การใช้ wait-and-notify
ให้ได้ผล จำเป็นว่า สภาพแวดล้อมที่อนุญาตให้เรียก wait()/notify()
ได้นั้นจะต้องอยู่ภายใต้สภาพแวดล้อมที่มี lockให้
Thread ใด Thread หนึ่งเข้ามาทำงาน
นั่นคือต้องมีคุณสมบัติ synchronized จึงจะทำงานได้ถูกต้อง
object ทุกตัวมี method
wait()/notify() เนื่องจากเป็น method ที่ติดมากับ
class Object ซึ่ง class ทุกตัวบังคับสืบทอดมาอยู่แล้ว
การประกาศ wait() เป็นการบ่งบอกว่า ให้ Thread ใดๆ ที่เข้ามาเพื่อ execute ตัวมันนั้นให้เปลี่ยนสถานะเป็น
รอ และ lock ที่ตัวมันถืออยู่ก็จะคลายออกเพื่อให้ Thread
ตัวอื่นสามารถเข้ามาใช้งาน object ตัวนั้นได้
ส่วน method
notify() นั้นก็จะตรงกันข้ามกัน กล่าวคือเมื่อ Thread ใดๆ มาเรียกคำสั่งนี้ จะปลุกให้ Thread ที่เคยตกอยู่ในสภาพรอจาก
object นี้ตื่นขึ้นมาเพื่อทำงานที่ค้างไว้ต่อให้จบ
นิสิตจะได้ใช้ความรู้เรื่อง wait/notify ใน Lab ถัดไป
อะไรคือ SwingUtilities.invokeLater()
จากการทำ Lab ที่ผ่านๆ มา นับตั้งแต่ Lab Swing GUI เป็นต้นมา
มีนิสิตหลายคนถามพี่ว่า SwingUtilities.invokeLater() มันคืออะไร
ทำไมต้องมี พี่ก็พยายามจะอธิบาย แต่น้องจำเป็นต้องมีความรู้เรื่อง Thread เสียก่อนจึงจะเข้าใจเรื่องนี้ได้อย่างชัดเจน
การโปรแกรม Swing นั้นต้องเข้าใจหลักการหลายๆ ข้อ หนึงในนั้นคือต้องเข้าใจหลักการทำงานของ Thread
AWT-EventQueue-0 ด้วย Thread นี้
นิสิตเคยยุ่งเกี่ยวมาแล้ว แต่อาจจะไม่รู้ตัว นั่นคือ Thread นี้ทำหน้าที่
execute Event Handling โค้ดที่เราเขียนขึ้น
รวมถึงจัดการอัปเดตการแสดงผล component แต่ละตัวด้วย
กฎข้อนี้กล่าวไว้ว่า
หาก component ใดๆ
ที่ถูก paint ไปแล้ว (หลังจากการเรียก pack(),setVisible(true)
) component นั้นๆ ต่อไปหากต้องการ update หน้าตาการแสดงผลจะต้องทำผ่าน
Thread AWT-EventQueue-0 เท่านั้น
เพราะฉะนั้นจึงมีทางเลือกในการ
update
อยู่ 2 ทางหลักๆ นั่นคือผ่านส่วนของ EventHandling
ที่รับประกันว่า EventQueue จะมาเรียกแน่นอน
และอีกวิธีนึง คือการทำผ่าน SwingUtilities.invokeLater()
เนื่องจากบางทีการ
update
component ไม่ได้เกิดขึ้นจาก event ที่ผู้ใช้กระทำอย่างเดียวเสมอ
ยกตัวอย่างภายใน Lab นี้ คือ progressBar เป็นต้น ตัว progressBar นั้น พี่ได้เขียนโค้ด Timer
ไว้ให้ ซึ่ง Timer นั้นก็ใช้อัปเดตการแสดงผลตามเวลาที่กำหนด
หลักการทำงานภายในนั้นมันไปเรียกใช้ SwingUtilities.invokeLater() นั่นเอง
หลักการทำงานของ
invokeLater(Runnable)
ก็ไม่ซับซ้อนอะไรมาก กล่าวคือ Runnable Thread ส่วนที่รับมา จะถูกเอาไปเข้าคิว EventQueue ไว้ให้
และเมื่อถึงเวลา Thread EventQueue ก็จะทำหน้าที่ execute
โค้ดส่วนนั้นเอง
ที่ผ่านมานิสิตสร้าง
GUI ผ่าน constructor เป็นหลัก
ซึ่งโดยปกติมันคือการใช้ Thread-main ทำหน้าที่ execute
โค้ดส่วนนั้น ที่มันยังทำงานได้ถูกต้องอยู่เป็นเพราะว่า component
ต่างๆ ยังไม่ถูก paint ลงไป แต่หลังจาก component
ถูก pack หรือ setVisible ไปแล้ว นิสิตจะไม่สามารถ update component โดยตรงจาก
Thread อื่นได้อีกเลย
ที่
main หลักเวลาจะเรียกโปรแกรม GUI ขึ้น หากเราเรียก setVisible(true) โดยตรง
บางครั้งโปรแกรมก็ทำงานได้ถูกต้อง แต่เพื่อกันการเกิดปัญหาที่อาจจะเกิดได้
จึงกำหนดไว้เป็นลักษณะการเขียนโปรแกรมที่ดี นั่นคือให้เรียกผ่าน invokeLater()
เสมอ
Observer Pattern
Observer
Pattern เป็นหนึ่งใน Pattern ที่ถูกใช้มากที่สุด
และมีประโยชน์มากตัวหนึ่ง Pattern นี้ออกแบบมาเพื่อที่จะแก้ปัญหากรณีที่
object ใดๆ
ต้องการกลวิธีในการรับรู้ว่ามีเหตุการณ์ที่ตัวเองสนใจเกิดขึ้นเมื่อไหร่
โดยเหตุการณ์ที่สนใจนั้นเป็นสิ่งที่ไม่สามารถควบคุมพฤติกรรมการเกิดได้
ในการออกแบบโปรแกรมเชิงวัตถุ
ผู้ออกแบบมักจะแยกโปรแกรมออกเป็นคลาสต่างๆเพื่อให้เกิดประโยชน์ในแง่ของการดูแลรักษาและการเสริมขยายโปรแกรมได้ง่าย
แต่ผลข้างเคียงที่เกิดขึ้นจากการแยกการทำงานออกเป็นส่วนๆ
ทำให้ผู้ออกแบบต้องมีวิธีการบริหารความสอดคล้องระหว่างอ็อบเจกต์ที่สัมพันธ์กันอย่างมีประสิทธิภาพ
โดยหลีกเลี่ยงการทำให้อ็อบเจกต์ขึ้นต่อกันโดยตรงอันจะส่งผลให้ความสามารถในการใช้งานซ้ำ
(reusability)
ถดถอยลงได้
ยกตัวอย่างในทางรูปธรรม
ในชุดเครื่องมือพัฒนา GUI มักจะแยก view ต่อประสานออกจากส่วนข้อมูล
model ทำให้ทั้ง view และส่วน model สามารถนำไปใช้งานซ้ำโดยไม่ขึ้นต่อกันได้
แน่นอนมันสามารถใช้งานร่วมกันได้ด้วย จากรูปที่ 1 จะเห็นได้ว่า
view นำเสนอข้อมูล model ชุดเดียวกันในรูปแบบที่แตกต่างกันทั้งตาราง
กราฟแท่ง และกราฟวงกลมตามลำดับ หากเปรียบ view ตารางกับกราฟแท่งจะพบว่าต่างส่วนต่างไม่รู้จักการมีตัวตนของกันและกัน
แต่เมื่อผู้ใช้เปลี่ยนแปลงข้อมูล model จาก view ใดๆ view ที่เหลือสามารถรับรู้การเปลี่ยนแปลงนั้นๆ
และสามารถที่จะปรับการแสดงผลให้ทันสมัยได้อย่างถูกต้อง
Observer
Pattern ช่วยทำให้ตัวอย่างนี้ทำได้จริง โดยกุญแจที่สำคัญต่อ
Pattern นี้ประกอบไปด้วย subject และ observer
โดย subject อาจมี observer ได้มากกว่าหนึ่ง และ observer จะถูกแจ้งเตือนให้ทราบเมื่อมีเหตุการณ์ที่ตัวเองสนใจเกิดขึ้นเพื่อที่จะได้ดำเนินการตามสิ่งที่ตัวเองได้รับมอบหมายให้ทำ
จากรูปที่
2
แสดงแผนภาพคลาสของ Pattern นี้ subject
จะมีเมธอดสำหรับลงทะเบียน (attach) ถอดถอน (detach)
แจ้งเหตุ (notify) observer ส่วน observer
จะจัดสรรเมธอดไว้ทำหน้าที่เป็นส่วนต่อประสานให้ subject สามารถแจ้งเหตุกลับมาได้อย่างถูกต้อง (update)
จากรูปที่
3
แสดงแผนภาพการทำงานร่วมกันของ Pattern เมื่อ
subject พบว่าตัวเองอยู่ในสถานการณ์ที่ต้องแจ้งให้ observer ทราบ subject จะ (เรียกเมธอด notify)
ทำหน้าที่กระจายข่าวสารให้ observer ทั้งหลายทราบ
observer จะได้รับรายงานการเปลี่ยนแปลง (จากเมธอด
update) และหาก observer ต้องการทราบผลของการเปลี่ยนแปลงก็อาจจะเรียกกลับไปยัง
subject เพื่อเรียกดูค่า (ด้วยเมธอด getState)
ประเด็นสำคัญและสถานการณ์ที่ควรใช้
Observer Pattern สามารถแจกแจงได้ดังนี้
– ช่วยให้การขึ้นต่อกันระหว่าง subject และ observer อยู่ในรูปแบบนามธรรม (abstract ต่อกัน) กล่าวคือ subject ประพฤติและปฏิบัติต่อ
observer ทุกคนเท่าเทียม subject ไม่จำเป็นต้องรับรู้ว่า
observer จริงๆ แล้วทำหน้าที่อะไร
ต้องการข้อมูลการแจ้งเตือนไปใช้ประโยชน์อะไร
– สนับสนุนการสื่อสารแบบกระจาย
กล่าวคือ การส่งข้อมูลการแจ้งเตือนของ subject ไปยัง
observer ไม่จำกัดจำนวน และ subject สามารถเพิ่มลดจำนวน
observer ได้ทุกเมื่อ
ส่วนจะมีปฏิกิริยาสนองกลับหรือเพิกเฉยต่อการแจ้งเตือนเป็นสิทธิ์และหน้าที่ของ
observer ใดๆ
เราจะพบเห็นตัวอย่างการใช้งาน Observer
Pattern อย่างหนักใน Swing ในส่วนที่เราคุ้นเคยนั่นคือการรายงานการเกิด
Event ต่างๆ เช่น เรากำหนดปุ่มให้เป็น subject แล้วเราทำตัวเองเป็น observer เพื่อดักฟังเหตการณ์การกดปุ่ม
โดยการสร้าง ActionListener ขึ้นมาแล้วนำมันไปลงทะเบียนผ่าน method
addActionListener เป็นต้น
ใน Lab นี้
ให้นิสิตนำความรู้เรื่อง Observer Pattern นี้มาใช้ด้วย
กล่าวคือ ให้มอง Horse แต่ละตัวเป็น subject และให้ HorseRaceFrame เป็น observer เพื่อคอยสังเกตการณ์การเปลี่ยนค่าสถานะของ horse ซึ่งมีค่าได้ดังนี้
-
สถานะการแข่งขัน
o
เริ่มต้น
o
ถึงเส้นชัย
-
สถานะของการเคลื่อนที่
o
หยุด
o
วิ่ง
โดยสถานะที่ HorseRaceFrame
ให้ความสนใจเป็นพิเศษนั่นคือสถานะการเข้าเส้นชัย
เพื่อนำมาใช้รายงานว่าม้าตัวไหนเข้าวินมาอันดับที่เท่าไหร่
เทคนิกการใช้ Observer
Pattern ร่วมกับ Thread นั้นจะพบได้ทั่วไป
อันเนื่องจากเมื่อ object อยู่ต่าง Thread กันทำให้ลำดับการทำงานแยกอิสระจากกันชัดเจน หาก object จาก Thread หนึ่งมีความสัมพันธ์ขึ้นกับ object
อีก Thread หนึ่ง การใช้วิธี polling เพื่อคอยสอบถามเป็นระยะๆ
ว่าเหตุการณ์ที่สนใจเกิดขึ้นหรือยังเป็นวิธีการแก้ปัญหาที่ไม่ดีนัก กลับกันหากใช้ Observer
Pattern จะเป็นสิ่งที่ตรงตัวและเป็นสามัญสำนึกกว่า กล่าวคือให้ผู้สนใจลงทะเบียนเอาไว้
และเมื่อเหตุการณ์ที่สนใจเกิดขึ้นเจ้าของเหตุการณ์จะแจ้งเตือนกลับไปยัง method
callback ที่ได้ตกลงกันไว้ภายหลัง
Pattern นี้นอกจากช่วยเรื่องการแจ้งเตือนให้เป็นไปได้แล้ว
ยังไม่ทำให้ object ใดๆ ผูกกันตรงๆ อันจะมีผลเสียทำให้โปรแกรม
reuse ได้ยาก แต่จะมีความสัมพันธ์ผ่านทาง Interface ที่ได้ตกลงกันไว้ ทำให้ต่างฝ่ายต่างไม่ต้องสนใจว่าอีกฝ่ายนึงเป็นใคร แล้วเอาข้อมูลของเราไปใช้ประโยชน์อะไรจากการแจ้งเตื่อนนั้นๆ
การส่งงาน
ให้สร้าง EXECUTABLE
Jar file แล้วส่งมาที่ progmethcp@gmail.com ภายในวันอังคารที่
11 กันยายน 2550 เวลา 24.00 น.
ไฟล์จะต้องตั้งชื่อว่า lab11_xxxxxxxxxx.jar โดย xxxxxxxxxx
นั้นเป็นเลขประจำตัวนิสิต ส่วนใน subject ของเมล์
ให้ใส่ lab11_ xxxxxxxxxx_Time โดย xxxxxxxxxx นั้นเป็นเลขประจำตัวนิสิต และ Time เป็นเวลา
(หน่วยเป็นชั่วโมง:นาที โดยประมาณ) ที่นิสิตใช้ในการทำแล็บนี้