ในบทความก่อนหน้านี้ฉันแนะนำวิธีการใช้ Java8 เพื่อใช้รูปแบบผู้สังเกตการณ์ (ตอนที่ 1) บทความนี้ยังคงแนะนำความรู้ที่เกี่ยวข้องของรูปแบบผู้สังเกตการณ์ Java8 เนื้อหาเฉพาะมีดังนี้:
การใช้งานแบบเธรดที่ปลอดภัย
บทก่อนหน้านี้แนะนำการดำเนินการตามรูปแบบผู้สังเกตการณ์ในสภาพแวดล้อม Java ที่ทันสมัย แม้ว่ามันจะง่าย แต่สมบูรณ์ แต่การใช้งานนี้ไม่สนใจปัญหาสำคัญ: ความปลอดภัยของเธรด แอปพลิเคชัน Java ที่เปิดกว้างส่วนใหญ่มีหลายเธรดและโหมดผู้สังเกตการณ์ส่วนใหญ่จะใช้ในระบบมัลติเธรดหรืออะซิงโครนัส ตัวอย่างเช่นหากบริการภายนอกอัปเดตฐานข้อมูลแอปพลิเคชันจะได้รับข้อความแบบอะซิงโครนัสแล้วแจ้งให้ส่วนประกอบภายในอัปเดตในโหมดผู้สังเกตการณ์แทนที่จะลงทะเบียนโดยตรงและฟังบริการภายนอก
ความปลอดภัยของเธรดในโหมดผู้สังเกตการณ์ส่วนใหญ่มุ่งเน้นไปที่ร่างกายของโหมดเนื่องจากความขัดแย้งของเธรดมีแนวโน้มที่จะเกิดขึ้นเมื่อแก้ไขคอลเลกชันผู้ฟังที่ลงทะเบียน ตัวอย่างเช่นหนึ่งเธรดพยายามเพิ่มผู้ฟังใหม่ในขณะที่เธรดอื่น ๆ พยายามเพิ่มวัตถุสัตว์ใหม่ซึ่งก่อให้เกิดการแจ้งเตือนไปยังผู้ฟังที่ลงทะเบียนทั้งหมด เมื่อพิจารณาจากลำดับของลำดับเธรดแรกอาจหรือไม่อาจเสร็จสิ้นการลงทะเบียนผู้ฟังใหม่ก่อนที่ผู้ฟังที่ลงทะเบียนจะได้รับการแจ้งเตือนของสัตว์ที่เพิ่มเข้ามา นี่เป็นกรณีคลาสสิกของการแข่งขันทรัพยากรเธรดและเป็นปรากฏการณ์นี้ที่บอกนักพัฒนาว่าพวกเขาต้องการกลไกเพื่อความปลอดภัยของเธรด
ทางออกที่ง่ายที่สุดสำหรับปัญหานี้คือ: การดำเนินการทั้งหมดที่เข้าถึงหรือแก้ไขรายการฟังการลงทะเบียนจะต้องเป็นไปตามกลไกการซิงโครไนซ์ Java เช่น::
Public Synchronized AnimaladdedListener RegisteranimaladdedListener (AnimaladdedListener Listener) {/*...*/} โมฆะสาธารณะไม่ได้เป็นโมฆะ unregisteranimaldedlistener (AnimaladdedListener) {/* ..ด้วยวิธีนี้ในเวลาเดียวกันมีเพียงเธรดเดียวเท่านั้นที่สามารถแก้ไขหรือเข้าถึงรายการผู้ฟังที่ลงทะเบียนซึ่งสามารถหลีกเลี่ยงปัญหาการแข่งขันทรัพยากรได้สำเร็จ แต่ปัญหาใหม่เกิดขึ้นและข้อ จำกัด ดังกล่าวเข้มงวดเกินไป (สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคำหลักที่ซิงโครไนซ์ ผ่านการซิงโครไนซ์วิธีการเข้าถึงรายการผู้ฟังพร้อมกันสามารถสังเกตได้ตลอดเวลา การลงทะเบียนและเพิกถอนผู้ฟังเป็นการดำเนินการเขียนสำหรับรายการผู้ฟังในขณะที่แจ้งให้ผู้ฟังเข้าถึงรายการผู้ฟังเป็นการดำเนินการแบบอ่านอย่างเดียว เนื่องจากการเข้าถึงผ่านการแจ้งเตือนเป็นการดำเนินการอ่านการดำเนินการแจ้งเตือนหลายครั้งสามารถดำเนินการพร้อมกันได้
ดังนั้นตราบใดที่ไม่มีการลงทะเบียนผู้ฟังหรือการเพิกถอนตราบใดที่การลงทะเบียนไม่ได้ลงทะเบียนตราบใดที่การแจ้งเตือนพร้อมกันจำนวนมากสามารถดำเนินการพร้อมกันได้โดยไม่ต้องเรียกใช้การแข่งขันทรัพยากรสำหรับรายชื่อผู้ฟังที่ลงทะเบียน แน่นอนว่าการแข่งขันทรัพยากรในสถานการณ์อื่น ๆ มีมานานแล้ว เพื่อแก้ปัญหานี้การล็อคทรัพยากรสำหรับ ReadWriteLock ได้รับการออกแบบมาเพื่อจัดการการดำเนินการอ่านและเขียนแยกต่างหาก รหัสการใช้งาน Threadsafe Safe Safe Safe ของคลาส Zoo Class มีดังนี้:
ชั้นเรียนสาธารณะ Threadsafezoo {ส่วนตัวสุดท้าย readWriteLock readWriteLock = ใหม่ reentRantReadWriteLock (); ได้รับการป้องกันการล็อคสุดท้าย readlock = readWriteLock.readlock (); ได้รับการป้องกันการล็อคสุดท้าย writelock = readWriteLock.writeLock (); รายการส่วนตัว <Anity> สัตว์ = arrayList ใหม่ <> (); รายการส่วนตัว <AnityAddedListener> ผู้ฟัง = new ArrayList <> (); void public Addanimal (สัตว์) {// เพิ่มรายการสัตว์ในรายการสัตว์ ListenerThis.NotifyanimaladdedListeners (Animal);} Public AnimaldedListener RegisteranimaladdedListener (AnimaladdedListener Listener) {// ล็อครายชื่อผู้ฟังเพื่อเขียนสิ่งนี้ writeLock.lock (); ลอง {// lockthis.writeLock.unlock ();} return listener;} โมฆะสาธารณะ unregisteranimaladdedlistener (imnemaddedListener Listener) {// ล็อครายการผู้ฟังเพื่อเขียนสิ่งนี้ writeLock.lock (); ลอง {// ลบผู้ฟังออกจากรายการ lockthis.writeLock.unlock ();}} โมฆะสาธารณะ notifyanimaladdedListeners (สัตว์สัตว์) {// ล็อครายการผู้ฟังสำหรับการอ่านนี้ readlock.lock (); ลอง {// // แจ้งผู้ฟังแต่ละคนในรายการผู้ฟังที่ลงทะเบียน ปลดล็อก reader lockthis.readlock.unlock ();}}}ผ่านการปรับใช้ดังกล่าวการใช้งานเรื่องสามารถมั่นใจได้ว่าความปลอดภัยของเธรดและหลายเธรดสามารถออกการแจ้งเตือนในเวลาเดียวกัน แต่ถึงกระนั้นก็ยังมีปัญหาการแข่งขันทรัพยากรสองอย่างที่ไม่สามารถเพิกเฉยได้:
การเข้าถึงผู้ฟังแต่ละคนพร้อมกัน หลายเธรดสามารถแจ้งผู้ฟังได้ว่าจำเป็นต้องใช้สัตว์ใหม่ซึ่งหมายความว่าผู้ฟังอาจถูกเรียกโดยหลายเธรดในเวลาเดียวกัน
การเข้าถึงรายการสัตว์พร้อมกัน หลายเธรดอาจเพิ่มวัตถุในรายการสัตว์ในเวลาเดียวกัน หากคำสั่งของการแจ้งเตือนมีผลกระทบอาจนำไปสู่การแข่งขันของทรัพยากรซึ่งต้องใช้กลไกการประมวลผลการดำเนินการพร้อมกันเพื่อหลีกเลี่ยงปัญหานี้ หากรายชื่อผู้ฟังที่ลงทะเบียนได้รับการแจ้งเตือนเพื่อเพิ่ม Animal2 แล้วได้รับการแจ้งเตือนเพื่อเพิ่ม Animal1 การแข่งขันทรัพยากรจะเกิดขึ้น อย่างไรก็ตามหากการเพิ่ม Animal1 และ Animal2 นั้นดำเนินการโดยเธรดที่แตกต่างกันก็เป็นไปได้ที่จะเพิ่มการเพิ่ม Animal1 ก่อน Animal2 โดยเฉพาะอย่างยิ่งเธรด 1 เพิ่ม Animal1 ก่อนที่จะแจ้งผู้ฟังและล็อคโมดูลเธรด 2 เพิ่ม Animal2 และแจ้งให้ผู้ฟังทราบแล้วเธรด 1 จะแจ้งผู้ฟังว่า Animal1 ได้รับการเพิ่ม แม้ว่าการแข่งขันทรัพยากรจะถูกเพิกเฉยเมื่อไม่ได้รับการพิจารณาลำดับของลำดับ แต่ปัญหานั้นเป็นเรื่องจริง
การเข้าถึงผู้ฟังพร้อมกัน
ผู้ฟังการเข้าถึงที่เกิดขึ้นพร้อมกันสามารถนำไปใช้งานได้โดยการสร้างความมั่นใจในความปลอดภัยของด้ายของผู้ฟัง ยึดมั่นในจิตวิญญาณของ "การตอบสนองต่อตนเอง" ของชั้นเรียนผู้ฟังมี "ข้อผูกพัน" เพื่อให้แน่ใจว่ามีความปลอดภัยในหัวข้อของตัวเอง ตัวอย่างเช่นสำหรับผู้ฟังที่นับข้างต้นการเพิ่มหรือลดจำนวนสัตว์โดยหลายเธรดอาจนำไปสู่ปัญหาความปลอดภัยของด้าย เพื่อหลีกเลี่ยงปัญหานี้การคำนวณจำนวนสัตว์จะต้องเป็นการดำเนินการอะตอม (ตัวแปรอะตอมหรือการซิงโครไนซ์วิธีการ) รหัสโซลูชันเฉพาะมีดังนี้:
ชั้นเรียนสาธารณะ Threadsafecountinganimaladdeddlistener ใช้ AnimaladdedListener {สัตว์อะตอมมิกตงแบบคงที่ส่วนตัว = Atomiclong ใหม่ (0);@overridepublic เป็นโมฆะ updateanimaladded (สัตว์สัตว์) {// เพิ่มจำนวนสัตว์ out.out.outรหัสโซลูชันการซิงโครไนซ์วิธีการดังต่อไปนี้:
การนับคลาสสาธารณะนับ AnimaladdedListener ใช้ AnimalAddedListener {สัตว์ int คงที่ส่วนตัว@@Overridepublic synchronized void updateanimaladded (สัตว์สัตว์) {// เพิ่มจำนวนสัตว์ out.out.println ("สัตว์รวม:"ควรเน้นว่าผู้ฟังควรรับรองความปลอดภัยของด้ายของตัวเอง หัวเรื่องจำเป็นต้องเข้าใจตรรกะภายในของผู้ฟังแทนที่จะสร้างความมั่นใจในความปลอดภัยของเธรดสำหรับการเข้าถึงและแก้ไขผู้ฟัง มิฉะนั้นหากหลายวิชาแบ่งปันผู้ฟังเดียวกันแต่ละคลาสคลาสจะต้องเขียนรหัสที่ปลอดภัยของเธรด เห็นได้ชัดว่ารหัสดังกล่าวไม่กระชับเพียงพอดังนั้นต้องใช้เธรดที่ปลอดภัยในคลาสผู้ฟัง
สั่งการแจ้งเตือนผู้ฟัง
เมื่อผู้ฟังจำเป็นต้องดำเนินการอย่างเป็นระเบียบการอ่านและการเขียนล็อคไม่สามารถตอบสนองความต้องการได้และต้องมีการแนะนำกลไกใหม่เพื่อให้แน่ใจว่าลำดับการโทรของฟังก์ชั่นการแจ้งเตือนนั้นสอดคล้องกับลำดับที่สัตว์เพิ่มเข้าไปในสวนสัตว์ บางคนพยายามใช้มันโดยใช้วิธีการซิงโครไนซ์ แต่จากการแนะนำวิธีการซิงโครไนซ์วิธีการในเอกสาร Oracle พบว่าการซิงโครไนซ์วิธีการไม่ได้ให้การจัดการคำสั่งของการดำเนินการปฏิบัติการ มันมั่นใจได้ว่าการดำเนินการอะตอมจะไม่ถูกขัดจังหวะและไม่รับประกันคำสั่งเธรดของการดำเนินการครั้งแรกมาก่อน (FIFO) ReentRantReadWriteLock สามารถใช้ลำดับการดำเนินการดังกล่าวได้รหัสดังต่อไปนี้:
คลาสสาธารณะ orderedThreadSafezoo {ส่วนตัวสุดท้าย readWriteLock ReadWriteLock = ใหม่ reentRantReadWriteLock (จริง); ได้รับการป้องกันการล็อคสุดท้าย readlock = readWriteLock.readlock (); ได้รับการป้องกันการล็อคสุดท้าย writelock = readWriteLock.writeLock (); รายการส่วนตัว <Anity> สัตว์ = arrayList ใหม่ <> (); รายการส่วนตัว <AnityAddedListener> ผู้ฟัง = new ArrayList <> (); void public Addanimal (สัตว์) {// เพิ่มรายการสัตว์ในรายการสัตว์ ListenerThis.NotifyanimaladdedListeners (Animal);} Public AnimaldedListener RegisteranimaladdedListener (AnimaladdedListener Listener) {// ล็อครายชื่อผู้ฟังเพื่อเขียนสิ่งนี้ writeLock.lock (); ลอง {// lockthis.writeLock.unlock ();} return listener;} โมฆะสาธารณะ unregisteranimaladdedlistener (imnemaddedListener Listener) {// ล็อครายการผู้ฟังเพื่อเขียนสิ่งนี้ writeLock.lock (); ลอง {// ลบผู้ฟังออกจากรายการ lockthis.writeLock.unlock ();}} โมฆะสาธารณะ notifyanimaladdedListeners (สัตว์สัตว์) {// ล็อครายการผู้ฟังสำหรับการอ่านนี้ readlock.lock (); ลอง {// // แจ้งผู้ฟังแต่ละคนในรายการผู้ฟังที่ลงทะเบียน ปลดล็อก reader lockthis.readlock.unlock ();}}}ด้วยวิธีนี้การลงทะเบียนฟังก์ชั่นการยกเลิกการลงทะเบียนและการแจ้งเตือนจะได้รับสิทธิ์การอ่านและเขียนล็อคตามลำดับแรกในครั้งแรก (FIFO) ตัวอย่างเช่นเธรด 1 ลงทะเบียนผู้ฟังเธรด 2 พยายามแจ้งผู้ฟังที่ลงทะเบียนหลังจากเริ่มต้นการดำเนินการลงทะเบียนเธรด 3 พยายามแจ้งผู้ฟังที่ลงทะเบียนเมื่อเธรด 2 กำลังรอการล็อคแบบอ่านอย่างเดียวโดยใช้วิธีการสั่งซื้อแบบยุติธรรม สิ่งนี้ทำให้มั่นใจได้ว่าลำดับการดำเนินการและลำดับเริ่มต้นของการกระทำนั้นสอดคล้องกัน
หากมีการนำวิธีการซิงโครไนซ์มาใช้แม้ว่าเธรด 2 คิวก่อนที่จะครอบครองทรัพยากรเธรด 3 อาจยังคงได้รับการล็อคทรัพยากรก่อนเธรด 2 และไม่สามารถรับประกันได้ว่าเธรด 2 จะแจ้งผู้ฟังก่อนมากกว่าเธรด 3 กุญแจสำคัญคือ: วิธีการสั่งซื้อแบบยุติธรรมสามารถมั่นใจได้ว่าเธรดจะดำเนินการตามลำดับ กลไกการสั่งซื้อของการอ่านและการเขียนล็อคนั้นซับซ้อนมาก คุณควรอ้างถึงเอกสารอย่างเป็นทางการของ ReentRantReadWriteLock เพื่อให้แน่ใจว่าตรรกะของการล็อคนั้นเพียงพอที่จะแก้ปัญหาได้
ความปลอดภัยของเธรดได้ถูกนำมาใช้และข้อดีและข้อเสียของการแยกตรรกะของหัวข้อและห่อหุ้มคลาส MixIn ลงในหน่วยรหัสที่ทำซ้ำได้จะถูกนำมาใช้ในบทต่อไปนี้
ธีมตรรกะที่ห่อหุ้มอยู่ในคลาส Mixin
มันน่าสนใจมากที่จะห่อหุ้มการออกแบบรูปแบบผู้สังเกตการณ์ที่กล่าวถึงข้างต้นลงในคลาส Mixin Target โดยทั่วไปผู้สังเกตการณ์ในโหมดผู้สังเกตการณ์มีคอลเลกชันของผู้ฟังที่ลงทะเบียน ลงทะเบียนฟังก์ชั่นที่รับผิดชอบในการลงทะเบียนผู้ฟังใหม่ ฟังก์ชั่นที่ไม่ลงทะเบียนรับผิดชอบในการเพิกถอนฟังก์ชั่นที่ไม่ลงทะเบียนที่ลงทะเบียนและแจ้งฟังก์ชั่นที่รับผิดชอบในการแจ้งผู้ฟัง สำหรับตัวอย่างข้างต้นของสวนสัตว์การดำเนินการอื่น ๆ ทั้งหมดของคลาสสวนสัตว์ยกเว้นว่ารายการสัตว์จำเป็นสำหรับปัญหาคือการใช้ตรรกะของเรื่อง
กรณีของคลาส Mixin แสดงอยู่ด้านล่าง ควรสังเกตว่าเพื่อให้รหัสกระชับมากขึ้นรหัสเกี่ยวกับความปลอดภัยของเธรดจะถูกลบออกที่นี่:
บทคัดย่อระดับสาธารณะคลาส ObservablesubjectMixin <SistenerType> {รายการส่วนตัว <SistererType> ผู้ฟัง = arrayList ใหม่ <> (); ผู้ฟังสาธารณะ registerListener (ฟัง ListenerType) (ListenerType Listener) {// ลบผู้ฟังออกจากรายชื่อผู้ฟังที่ลงทะเบียนแล้ว listeners.remove (ผู้ฟัง);} โมฆะสาธารณะ NotifyListeners (ผู้บริโภค <?เนื่องจากไม่ได้ให้ข้อมูลอินเทอร์เฟซของประเภทผู้ฟังที่ลงทะเบียนผู้ฟังเฉพาะไม่สามารถแจ้งให้ทราบได้โดยตรงดังนั้นจึงจำเป็นเพื่อให้แน่ใจว่าความเป็นสากลของฟังก์ชั่นการแจ้งเตือนและอนุญาตให้ลูกค้าเพิ่มฟังก์ชั่นบางอย่างเช่นการยอมรับการจับคู่พารามิเตอร์ของประเภทพารามิเตอร์ทั่วไปที่จะใช้กับผู้ฟังแต่ละคน รหัสการใช้งานเฉพาะมีดังนี้:
ชั้นเรียนสาธารณะ zoousingmixin ขยาย ObservablesubjectMixin <AnimalAddedListener> {รายการส่วนตัว <Anity> สัตว์ = arrayList ใหม่ <> (); โมฆะสาธารณะ addanimal (สัตว์สัตว์) {// เพิ่มสัตว์ลงในรายการสัตว์ Listener.updateanimaladded (Animal));}}ข้อได้เปรียบที่ใหญ่ที่สุดของเทคโนโลยี MixIn Class คือการห่อหุ้มตัวแบบที่มีรูปทรงของผู้สังเกตการณ์ลงในคลาสที่ทำซ้ำได้แทนที่จะทำซ้ำตรรกะในแต่ละคลาส นอกจากนี้วิธีนี้ทำให้การใช้งานคลาส Zoo ง่ายขึ้นเพียงจัดเก็บข้อมูลสัตว์โดยไม่ต้องพิจารณาวิธีการจัดเก็บและแจ้งผู้ฟัง
อย่างไรก็ตามการใช้คลาส Mixin ไม่ได้เป็นเพียงข้อได้เปรียบ ตัวอย่างเช่นถ้าคุณต้องการจัดเก็บผู้ฟังหลายประเภท ตัวอย่างเช่นจำเป็นต้องจัดเก็บประเภทผู้ฟัง AnimalRemovedListener คลาส Mixin เป็นคลาสนามธรรม คลาสนามธรรมหลายคลาสไม่สามารถสืบทอดได้ในเวลาเดียวกันใน Java และคลาส Mixin ไม่สามารถนำไปใช้ได้โดยใช้อินเทอร์เฟซแทน นี่เป็นเพราะอินเทอร์เฟซไม่มีสถานะและสถานะในโหมดผู้สังเกตการณ์จะต้องใช้เพื่อบันทึกรายการผู้ฟังที่ลงทะเบียน
ทางออกหนึ่งคือการสร้าง Zoolistener ประเภทผู้ฟังที่จะได้รับแจ้งเมื่อสัตว์เพิ่มขึ้นและลดลง รหัสมีลักษณะเช่นนี้:
Interface Public Zoolistener {โมฆะสาธารณะ onanimaladded (สัตว์สัตว์); โมฆะสาธารณะ onanimalremoved (สัตว์สัตว์);}ด้วยวิธีนี้คุณสามารถใช้อินเทอร์เฟซนี้เพื่อใช้การตรวจสอบการเปลี่ยนแปลงต่าง ๆ ในสถานะสวนสัตว์โดยใช้ประเภทผู้ฟัง:
ระดับสาธารณะ zoousingmixin ขยาย ObservablesUbjectMixin <Zoolistener> {รายการส่วนตัว <Anity> สัตว์ = arrayList ใหม่ <> (); โมฆะสาธารณะ addanimal (สัตว์สัตว์) {// เพิ่มสัตว์ลงในรายการสัตว์ listener.onanimaladded (สัตว์));} โมฆะสาธารณะ removeanimal (สัตว์สัตว์) {// ลบสัตว์ออกจากรายการสัตว์ที่ได้รับ removere (สัตว์); // แจ้งรายการของผู้ฟังที่ลงทะเบียนการรวมผู้ฟังหลายประเภทลงในอินเทอร์เฟซผู้ฟังหนึ่งตัวจะแก้ปัญหาที่กล่าวถึงข้างต้น แต่ยังมีข้อบกพร่องซึ่งจะกล่าวถึงในรายละเอียดในบทต่อไปนี้
ผู้ฟังและอะแดปเตอร์หลายวิธี
ในวิธีการข้างต้นหากอินเทอร์เฟซของผู้ฟังใช้ฟังก์ชั่นมากเกินไปอินเตอร์เฟสจะ verbose เกินไป ตัวอย่างเช่น Swing Mouselistener มี 5 ฟังก์ชั่นที่จำเป็น แม้ว่าคุณสามารถใช้หนึ่งในนั้นได้ แต่คุณต้องเพิ่มฟังก์ชั่น 5 ฟังก์ชั่นเหล่านี้ตราบเท่าที่คุณใช้เหตุการณ์การคลิกเมาส์ มีแนวโน้มที่จะใช้งานฟังก์ชั่นว่างเปล่าเพื่อใช้ฟังก์ชั่นที่เหลืออยู่ซึ่งจะนำความสับสนที่ไม่จำเป็นมาสู่รหัสอย่างไม่ต้องสงสัย
ทางออกหนึ่งคือการสร้างอะแดปเตอร์ (แนวคิดมาจากรูปแบบอะแดปเตอร์ที่เสนอโดย GOF) การดำเนินการของอินเทอร์เฟซผู้ฟังถูกนำมาใช้ในรูปแบบของฟังก์ชั่นนามธรรมสำหรับการสืบทอดของคลาสฟังที่เฉพาะเจาะจง ด้วยวิธีนี้คลาสผู้ฟังที่เฉพาะเจาะจงสามารถเลือกฟังก์ชั่นที่ต้องการและใช้การดำเนินการเริ่มต้นสำหรับฟังก์ชั่นที่ไม่จำเป็นโดยอะแดปเตอร์ ตัวอย่างเช่นในคลาส Zoolistener ในตัวอย่างด้านบนสร้าง Zooadapter (กฎการตั้งชื่อของอะแดปเตอร์นั้นสอดคล้องกับผู้ฟังคุณจะต้องเปลี่ยนผู้ฟังในชื่อคลาสเป็นอะแดปเตอร์) รหัสมีดังนี้:
คลาสสาธารณะ Zooadapter ใช้ Zoolistener {@Overridepublic เป็นโมฆะ onanimaladded (สัตว์สัตว์) {} @Overridepublic เป็นโมฆะ onanimalremoved (สัตว์สัตว์) {}}เมื่อมองแวบแรกคลาสอะแดปเตอร์นี้ไม่มีนัยสำคัญ แต่ความสะดวกสบายที่นำมาไม่สามารถประเมินได้ ตัวอย่างเช่นสำหรับคลาสเฉพาะต่อไปนี้เพียงเลือกฟังก์ชั่นที่เป็นประโยชน์กับพวกเขา:
คลาสสาธารณะ Nameprinterzooadapter ขยาย Zooadapter {@Overridepublic เป็นโมฆะ onanimaladded (สัตว์) {// พิมพ์ชื่อของสัตว์ที่ addSystem.out.println ("สัตว์ที่เพิ่มชื่อ" + iment.getName ());}}}}}}}}}}มีสองทางเลือกที่สามารถใช้ฟังก์ชั่นของคลาสอะแดปเตอร์: หนึ่งคือการใช้ฟังก์ชันเริ่มต้น; อีกอย่างคือการรวมอินเทอร์เฟซผู้ฟังและคลาสอะแดปเตอร์เข้ากับคลาสเฉพาะ ฟังก์ชั่นเริ่มต้นถูกเสนอใหม่โดย Java 8 ช่วยให้นักพัฒนาสามารถให้วิธีการใช้งานเริ่มต้น (การป้องกัน) ในอินเทอร์เฟซ
การอัปเดตนี้ไปยังไลบรารี Java นี้ส่วนใหญ่เพื่ออำนวยความสะดวกให้กับนักพัฒนาในการใช้งานส่วนขยายของโปรแกรมโดยไม่ต้องเปลี่ยนรหัสรุ่นเก่าดังนั้นวิธีนี้ควรใช้ด้วยความระมัดระวัง หลังจากใช้งานหลายครั้งนักพัฒนาบางคนจะรู้สึกว่ารหัสที่เขียนด้วยวิธีนี้ไม่เป็นมืออาชีพและนักพัฒนาบางคนคิดว่านี่เป็นคุณลักษณะของ Java 8 ไม่ว่าจะเกิดอะไรขึ้นพวกเขาจำเป็นต้องเข้าใจความตั้งใจดั้งเดิมของเทคโนโลยีนี้แล้วตัดสินใจว่าจะใช้ตามคำถามเฉพาะหรือไม่ รหัสอินเทอร์เฟซ Zoolistener ที่ใช้งานโดยใช้ฟังก์ชั่นเริ่มต้นมีดังนี้:
อินเทอร์เฟซสาธารณะ zoolistener {ค่าเริ่มต้นเป็นโมฆะสาธารณะ onanimaladded (สัตว์สัตว์) {} ค่าเริ่มต้นโมฆะสาธารณะ onanimalremoved (สัตว์สัตว์) {}}โดยการใช้ฟังก์ชั่นเริ่มต้นการใช้คลาสเฉพาะของอินเตอร์เฟสไม่จำเป็นต้องใช้ฟังก์ชั่นทั้งหมดในอินเทอร์เฟซ แต่แทนที่จะเลือกใช้ฟังก์ชันที่จำเป็น แม้ว่านี่จะเป็นทางออกที่ค่อนข้างง่ายสำหรับปัญหาการขยายส่วนต่อประสาน แต่นักพัฒนาควรให้ความสนใจมากขึ้นเมื่อใช้งาน
ทางออกที่สองคือการทำให้โหมดผู้สังเกตการณ์ง่ายขึ้นละเว้นอินเทอร์เฟซผู้ฟังและใช้คลาสเฉพาะเพื่อใช้ฟังก์ชั่นของผู้ฟัง ตัวอย่างเช่นอินเทอร์เฟซ Zoolistener กลายเป็นดังต่อไปนี้:
Zoolistener ระดับสาธารณะ {โมฆะสาธารณะ onanimaladded (สัตว์สัตว์) {} โมฆะสาธารณะ onanimalremoved (สัตว์สัตว์) {}}โซลูชันนี้ทำให้ลำดับชั้นของรูปแบบผู้สังเกตการณ์ง่ายขึ้น แต่ไม่สามารถใช้ได้กับทุกกรณีเพราะหากอินเทอร์เฟซของผู้ฟังถูกรวมเข้ากับคลาสเฉพาะผู้ฟังเฉพาะไม่สามารถใช้อินเทอร์เฟซการฟังหลายรายการได้ ตัวอย่างเช่นหาก AnimaladdedListener และ AnimalRemovedListener Interfaces ถูกเขียนขึ้นในคลาสคอนกรีตเดียวกันผู้ฟังที่เฉพาะเจาะจงเพียงคนเดียวไม่สามารถใช้อินเทอร์เฟซทั้งสองในเวลาเดียวกันได้ นอกจากนี้ความตั้งใจของอินเทอร์เฟซผู้ฟังนั้นชัดเจนกว่าของคลาสเฉพาะ เห็นได้ชัดว่าอดีตคือการให้อินเทอร์เฟซสำหรับคลาสอื่น ๆ แต่หลังไม่ชัดเจน
หากไม่มีเอกสารที่เหมาะสมนักพัฒนาจะไม่ทราบว่ามีคลาสที่มีบทบาทของอินเทอร์เฟซและใช้ฟังก์ชั่นที่สอดคล้องกันทั้งหมด นอกจากนี้ชื่อคลาสไม่ได้มีอะแดปเตอร์เนื่องจากคลาสไม่พอดีกับอินเทอร์เฟซที่แน่นอนดังนั้นชื่อคลาสไม่ได้หมายความถึงความตั้งใจนี้โดยเฉพาะ เพื่อสรุปปัญหาเฉพาะต้องเลือกวิธีการเฉพาะและไม่มีวิธีใดก็ตามที่มีอำนาจทุกอย่าง
ก่อนที่เราจะเริ่มบทต่อไปเป็นสิ่งสำคัญที่จะกล่าวถึงว่าอะแดปเตอร์เป็นเรื่องธรรมดาในโหมดการสังเกตโดยเฉพาะอย่างยิ่งในรหัส Java รุ่นเก่า Swing API ถูกนำไปใช้ตามอะแดปเตอร์เนื่องจากแอปพลิเคชันเก่า ๆ จำนวนมากใช้ในรูปแบบผู้สังเกตการณ์ใน Java 5 และ Java 6 ผู้ฟังในกรณีสวนสัตว์อาจไม่จำเป็นต้องใช้อะแดปเตอร์ แต่จำเป็นต้องเข้าใจวัตถุประสงค์ของอะแดปเตอร์และแอปพลิเคชันเพราะเราสามารถใช้มันในรหัสที่มีอยู่ บทต่อไปนี้จะแนะนำผู้ฟังที่ใช้เวลามาก ผู้ฟังประเภทนี้อาจดำเนินการใช้เวลานานหรือโทรแบบอะซิงโครนัสและไม่สามารถให้ค่าคืนได้ทันที
ผู้ฟังที่ซับซ้อนและบล็อก
ข้อสันนิษฐานหนึ่งเกี่ยวกับรูปแบบผู้สังเกตการณ์คือเมื่อมีการดำเนินการฟังก์ชั่นชุดของผู้ฟังจะถูกเรียก แต่สันนิษฐานว่ากระบวนการนี้โปร่งใสอย่างสมบูรณ์ต่อผู้โทร ตัวอย่างเช่นเมื่อรหัสลูกค้าเพิ่มสัตว์ในสวนสัตว์จะไม่ทราบว่าชุดของผู้ฟังจะถูกเรียกก่อนที่จะกลับมาประสบความสำเร็จ หากการดำเนินการของผู้ฟังใช้เวลานาน (เวลาได้รับผลกระทบจากจำนวนผู้ฟังเวลาดำเนินการของผู้ฟังแต่ละคน) รหัสลูกค้าจะรับรู้ถึงผลข้างเคียงของการเพิ่มขึ้นอย่างง่าย ๆ ในการดำเนินงานของสัตว์
บทความนี้ไม่สามารถพูดคุยหัวข้อนี้ได้อย่างครอบคลุม ต่อไปนี้เป็นสิ่งที่นักพัฒนาควรให้ความสนใจเมื่อโทรหาผู้ฟังที่ซับซ้อน:
ผู้ฟังเริ่มกระทู้ใหม่ หลังจากเธรดใหม่เริ่มต้นขึ้นในขณะที่ดำเนินการ LISSENER LOGIC ในเธรดใหม่ผลลัพธ์การประมวลผลของฟังก์ชั่นฟังจะถูกส่งคืนและผู้ฟังคนอื่นจะทำงาน
หัวเรื่องเริ่มเธรดใหม่ ซึ่งแตกต่างจากการวนซ้ำแบบดั้งเดิมของรายการผู้ฟังที่ลงทะเบียนแล้วฟังก์ชั่นการแจ้งเตือนของหัวเรื่องจะรีสตาร์ทเธรดใหม่แล้ววนซ้ำผ่านรายการผู้ฟังในเธรดใหม่ สิ่งนี้ช่วยให้ฟังก์ชั่นการแจ้งเตือนสามารถส่งออกค่าการส่งคืนในขณะที่ดำเนินการฟังผู้ฟังอื่น ๆ ควรสังเกตว่าจำเป็นต้องใช้กลไกความปลอดภัยของเธรดเพื่อให้แน่ใจว่ารายการผู้ฟังไม่ได้รับการแก้ไขพร้อมกัน
ผู้ฟังคิวโทรและทำฟังก์ชั่นการฟังด้วยชุดเธรด ห่อหุ้มการดำเนินการของผู้ฟังในฟังก์ชั่นบางอย่างและคิวพวกเขาแทนที่จะเรียกซ้ำ ๆ ไปยังรายการผู้ฟัง เมื่อผู้ฟังเหล่านี้ถูกเก็บไว้ในคิวเธรดสามารถปรากฏองค์ประกอบเดียวจากคิวและดำเนินการตรรกะการฟัง สิ่งนี้คล้ายกับปัญหาผู้ผลิต-ผู้บริโภค กระบวนการแจ้งเตือนจะสร้างคิวฟังก์ชั่นที่ใช้งานได้ซึ่งเธรดจะนำคิวออกมาและดำเนินการฟังก์ชั่นเหล่านี้ ฟังก์ชั่นจำเป็นต้องจัดเก็บเวลาที่ถูกสร้างขึ้นมากกว่าเวลาที่ดำเนินการเพื่อให้ฟังก์ชันฟังเรียกใช้ ตัวอย่างเช่นฟังก์ชั่นที่สร้างขึ้นเมื่อมีการเรียกฟังฟังก์ชั่นจะต้องจัดเก็บจุดในเวลา ฟังก์ชั่นนี้คล้ายกับการดำเนินการต่อไปนี้ใน Java:
Public Class Animaladdedfunctor {Private Final AnimaleddedListener Listener; พารามิเตอร์สัตว์สุดท้ายส่วนตัว; Animaladdedfunctor (AnimaladdedListener Listener, พารามิเตอร์สัตว์) {this.listener = ผู้ฟัง; parameter = พารามิเตอร์; creationthis.listener.updateanimaladded (this.parameter);}}ฟังก์ชั่นถูกสร้างและบันทึกในคิวและสามารถเรียกได้ตลอดเวลาเพื่อไม่ให้ดำเนินการที่เกี่ยวข้องทันทีเมื่อข้ามรายการรายการรายการ เมื่อแต่ละฟังก์ชั่นที่เปิดใช้งานผู้ฟังจะถูกผลักเข้าไปในคิว "เธรดผู้บริโภค" จะส่งคืนสิทธิ์ในการดำเนินงานไปยังรหัสลูกค้า "เธรดผู้บริโภค" จะดำเนินการฟังก์ชั่นเหล่านี้ในบางจุดในภายหลังราวกับว่าผู้ฟังถูกเปิดใช้งานโดยฟังก์ชั่นการแจ้งเตือน เทคโนโลยีนี้เรียกว่าพารามิเตอร์ที่มีผลผูกพันในภาษาอื่นซึ่งเหมาะกับตัวอย่างข้างต้น สาระสำคัญของเทคโนโลยีคือการบันทึกพารามิเตอร์ของผู้ฟังจากนั้นเรียกใช้ฟังก์ชัน Execute () โดยตรง หากผู้ฟังได้รับพารามิเตอร์หลายตัววิธีการประมวลผลจะคล้ายกัน
ควรสังเกตว่าหากคุณต้องการบันทึกลำดับการดำเนินการของผู้ฟังคุณต้องแนะนำกลไกการเรียงลำดับที่ครอบคลุม ใน Scheme 1 ผู้ฟังจะเปิดใช้งานเธรดใหม่ตามลำดับปกติซึ่งทำให้มั่นใจได้ว่าผู้ฟังจะดำเนินการตามลำดับการลงทะเบียน ใน Scheme 2 คิวรองรับการเรียงลำดับและฟังก์ชั่นในนั้นจะถูกดำเนินการตามลำดับที่พวกเขาเข้าสู่คิว พูดง่ายๆคือนักพัฒนาจำเป็นต้องให้ความสนใจกับความซับซ้อนของการดำเนินการหลายเธรดของผู้ฟังและจัดการอย่างรอบคอบเพื่อให้แน่ใจว่าพวกเขาใช้ฟังก์ชั่นที่จำเป็น
บทสรุป
ก่อนที่โมเดลผู้สังเกตการณ์จะถูกเขียนลงในหนังสือเล่มนี้ในปี 1994 มันเป็นรูปแบบการออกแบบซอฟต์แวร์หลักแล้วซึ่งเป็นโซลูชั่นที่น่าพอใจมากมายสำหรับปัญหาที่มักเกิดขึ้นในการออกแบบซอฟต์แวร์ Java เป็นผู้นำในการใช้รูปแบบนี้และห่อหุ้มรูปแบบนี้ในห้องสมุดมาตรฐาน แต่เนื่องจาก Java ได้รับการปรับปรุงเป็นเวอร์ชัน 8 จึงจำเป็นต้องตรวจสอบการใช้รูปแบบคลาสสิกอีกครั้ง ด้วยการเกิดขึ้นของการแสดงออกของแลมบ์ดาและโครงสร้างใหม่อื่น ๆ รูปแบบ "เก่า" นี้มีพลังใหม่ ไม่ว่าจะเป็นการจัดการโปรแกรมเก่า ๆ หรือใช้วิธีการที่ยืนยาวนี้เพื่อแก้ปัญหาใหม่โดยเฉพาะอย่างยิ่งสำหรับนักพัฒนา Java ที่มีประสบการณ์รูปแบบผู้สังเกตการณ์เป็นเครื่องมือหลักสำหรับนักพัฒนา
ONEAPM ให้บริการโซลูชั่นประสิทธิภาพการใช้งาน Java Application end-to-end เราสนับสนุนเฟรมเวิร์ก Java ทั่วไปทั้งหมดและเซิร์ฟเวอร์แอปพลิเคชันเพื่อช่วยให้คุณค้นพบคอขวดของระบบอย่างรวดเร็วและค้นหาสาเหตุของความผิดปกติ การปรับใช้ในระดับนาทีและประสบการณ์ทันทีการตรวจสอบ Java ไม่เคยง่ายกว่านี้มาก่อน หากต้องการอ่านบทความทางเทคนิคเพิ่มเติมโปรดเยี่ยมชมบล็อกเทคโนโลยีอย่างเป็นทางการของ OneAPM
เนื้อหาข้างต้นแนะนำวิธีการใช้ Java8 เพื่อใช้โหมดผู้สังเกตการณ์ (ตอนที่ 2) ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน!