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 เส้นไหนเป็นผู้ชนะและได้ครอบครองข้อมูลก่อนกัน

thread lab_html_78289804


วิธีการแก้ไข

เราสามารถแก้ไขปัญหา 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 เป็นเวลา (หน่วยเป็นชั่วโมง:นาที โดยประมาณ) ที่นิสิตใช้ในการทำแล็บนี้