โหมดผู้สังเกตการณ์หรือที่รู้จักกันในชื่อโหมดเผยแพร่/สมัครสมาชิกถูกเสนอโดยกลุ่มสี่คน (GOF คือ Erich Gamma, Richard Helm, Ralph Johnson และ John Vlissides) ในปี 1994 "รูปแบบการออกแบบ: พื้นฐานของซอฟต์แวร์เชิงวัตถุที่นำกลับมาใช้ใหม่ได้" (ดูหน้า 293-313 แม้ว่ารูปแบบนี้จะมีประวัติจำนวนมาก แต่ก็ยังคงมีผลบังคับใช้อย่างกว้างขวางกับสถานการณ์ที่หลากหลายและได้กลายเป็นส่วนสำคัญของห้องสมุด Java มาตรฐาน แม้ว่าจะมีบทความมากมายเกี่ยวกับรูปแบบผู้สังเกตการณ์ แต่พวกเขาทั้งหมดมุ่งเน้นไปที่การใช้งานใน Java แต่ไม่สนใจปัญหาต่าง ๆ ที่นักพัฒนาพบเมื่อใช้รูปแบบผู้สังเกตการณ์ใน Java
ความตั้งใจดั้งเดิมของการเขียนบทความนี้คือการเติมช่องว่างนี้: บทความนี้ส่วนใหญ่แนะนำการใช้รูปแบบผู้สังเกตการณ์โดยใช้สถาปัตยกรรม Java8 และสำรวจปัญหาที่ซับซ้อนเกี่ยวกับรูปแบบคลาสสิกบนพื้นฐานนี้รวมถึงชั้นเรียนภายในที่ไม่ระบุชื่อ แม้ว่าเนื้อหาของบทความนี้จะไม่ครอบคลุม แต่ปัญหาที่ซับซ้อนจำนวนมากที่เกี่ยวข้องในโมเดลนี้ไม่สามารถอธิบายได้ในบทความเดียว แต่หลังจากอ่านบทความนี้ผู้อ่านสามารถเข้าใจว่ารูปแบบผู้สังเกตการณ์คืออะไรความเป็นสากลใน Java และวิธีจัดการกับปัญหาที่พบบ่อยเมื่อใช้รูปแบบผู้สังเกตการณ์ใน Java
โหมดผู้สังเกตการณ์
ตามคำจำกัดความคลาสสิกที่เสนอโดย GOF ธีมของรูปแบบผู้สังเกตการณ์คือ:
กำหนดการพึ่งพาแบบหนึ่งถึงหลายคนระหว่างวัตถุ เมื่อสถานะของวัตถุเปลี่ยนแปลงวัตถุทั้งหมดที่ขึ้นอยู่กับมันจะได้รับแจ้งและอัปเดตโดยอัตโนมัติ
หมายความว่าอย่างไร? ในแอพพลิเคชั่นซอฟต์แวร์จำนวนมากรัฐระหว่างวัตถุจะพึ่งพาซึ่งกันและกัน ตัวอย่างเช่นหากแอปพลิเคชันมุ่งเน้นไปที่การประมวลผลข้อมูลเชิงตัวเลขข้อมูลนี้อาจแสดงผ่านตารางหรือแผนภูมิของส่วนต่อประสานกราฟิกผู้ใช้ (GUI) หรือใช้ในเวลาเดียวกันนั่นคือเมื่อข้อมูลพื้นฐานได้รับการปรับปรุงส่วนประกอบ GUI ที่สอดคล้องกันจะต้องได้รับการปรับปรุง กุญแจสำคัญในการแก้ไขปัญหาคือวิธีการอัปเดตข้อมูลพื้นฐานเมื่อส่วนประกอบ GUI ได้รับการอัปเดตและในขณะเดียวกันก็ลดการมีเพศสัมพันธ์ระหว่างส่วนประกอบ GUI และข้อมูลพื้นฐาน
วิธีแก้ปัญหาที่ง่ายและไม่สามารถปรับขนาดได้คือการอ้างถึงส่วนประกอบของตารางและภาพ GUI ของวัตถุที่จัดการข้อมูลพื้นฐานเหล่านี้เพื่อให้วัตถุสามารถแจ้งส่วนประกอบ GUI เมื่อข้อมูลพื้นฐานเปลี่ยนแปลง เห็นได้ชัดว่าโซลูชันที่เรียบง่ายนี้แสดงให้เห็นถึงข้อบกพร่องของแอปพลิเคชันที่ซับซ้อนซึ่งจัดการส่วนประกอบ GUI ได้มากขึ้น ตัวอย่างเช่นมี 20 องค์ประกอบ GUI ที่ทั้งหมดพึ่งพาข้อมูลพื้นฐานดังนั้นวัตถุที่จัดการข้อมูลพื้นฐานจำเป็นต้องรักษาข้อมูลอ้างอิงถึงองค์ประกอบ 20 รายการเหล่านี้ เมื่อจำนวนวัตถุขึ้นอยู่กับข้อมูลที่เกี่ยวข้องเพิ่มขึ้นระดับของการมีเพศสัมพันธ์ระหว่างการจัดการข้อมูลและวัตถุกลายเป็นเรื่องยากที่จะควบคุม
ทางออกที่ดีกว่าอีกทางหนึ่งคือการอนุญาตให้วัตถุลงทะเบียนเพื่อรับสิทธิ์ในการอัปเดตข้อมูลที่น่าสนใจซึ่งตัวจัดการข้อมูลจะแจ้งวัตถุเหล่านั้นเมื่อข้อมูลเปลี่ยนแปลง ในแง่ของ Layman ให้วัตถุข้อมูลที่น่าสนใจบอกผู้จัดการ: "โปรดแจ้งให้ฉันทราบเมื่อข้อมูลเปลี่ยนแปลง" นอกจากนี้วัตถุเหล่านี้ไม่เพียงสามารถลงทะเบียนเพื่อรับการแจ้งเตือนการอัปเดตเท่านั้น แต่ยังยกเลิกการลงทะเบียนเพื่อให้แน่ใจว่าตัวจัดการข้อมูลจะไม่แจ้งวัตถุอีกต่อไปเมื่อข้อมูลเปลี่ยนแปลง ในคำจำกัดความดั้งเดิมของ GOF วัตถุที่ลงทะเบียนเพื่อรับการอัปเดตเรียกว่า "ผู้สังเกตการณ์" ตัวจัดการข้อมูลที่เกี่ยวข้องเรียกว่า "หัวเรื่อง" ข้อมูลที่ผู้สังเกตการณ์สนใจเรียกว่า "สถานะเป้าหมาย" กระบวนการลงทะเบียนเรียกว่า "เพิ่ม" ดังที่ได้กล่าวไว้ข้างต้นโหมดผู้สังเกตการณ์เรียกอีกอย่างว่าโหมด Publish-Subscribe สามารถเข้าใจได้ว่าลูกค้าสมัครสมาชิกกับผู้สังเกตการณ์เกี่ยวกับเป้าหมาย เมื่อสถานะเป้าหมายได้รับการอัปเดตเป้าหมายจะเผยแพร่การอัปเดตเหล่านี้ไปยังสมาชิก (รูปแบบการออกแบบนี้จะขยายไปยังสถาปัตยกรรมทั่วไปที่เรียกว่าสถาปัตยกรรม Publish-Subscribe) แนวคิดเหล่านี้สามารถแสดงได้ด้วยแผนภาพคลาสต่อไปนี้:
concereteobserver ใช้เพื่อรับการเปลี่ยนแปลงสถานะการอัปเดตและส่งผ่านการอ้างอิงไปยัง conceretesubject ไปยังตัวสร้าง สิ่งนี้ให้การอ้างอิงถึงเรื่องที่เฉพาะเจาะจงสำหรับผู้สังเกตการณ์เฉพาะซึ่งสามารถรับการอัปเดตได้เมื่อรัฐเปลี่ยนแปลง พูดง่ายๆคือผู้สังเกตการณ์เฉพาะจะได้รับการบอกกล่าวให้อัปเดตหัวข้อและในขณะเดียวกันก็ใช้การอ้างอิงในคอนสตรัคเตอร์เพื่อให้ได้สถานะของหัวข้อเฉพาะและในที่สุดก็จัดเก็บวัตถุสถานะการค้นหาเหล่านี้ภายใต้คุณสมบัติการสังเกตการณ์ของผู้สังเกตการณ์เฉพาะ กระบวนการนี้แสดงในแผนภาพลำดับต่อไปนี้:
ความเป็นมืออาชีพของโมเดลคลาสสิก
แม้ว่าโมเดลผู้สังเกตการณ์จะเป็นสากล แต่ก็มีโมเดลพิเศษมากมาย
จัดเตรียมพารามิเตอร์ไปยังวัตถุสถานะส่งผ่านไปยังวิธีการอัปเดตที่เรียกโดยผู้สังเกตการณ์ ในโหมดคลาสสิกเมื่อผู้สังเกตการณ์ได้รับแจ้งว่าสถานะของเรื่องมีการเปลี่ยนแปลงสถานะที่อัปเดตจะได้รับโดยตรงจากเรื่อง สิ่งนี้ต้องการให้ผู้สังเกตการณ์บันทึกการอ้างอิงวัตถุไปยังสถานะที่ดึงมา สิ่งนี้เกิดจากการอ้างอิงแบบวงกลมการอ้างอิงของคอนกรีตพ. จุดไปยังรายการผู้สังเกตการณ์และการอ้างอิงของคอนกรีตบอสเซิร์ฟเวอร์ชี้ไปที่คอนกรีตที่สามารถรับสถานะเรื่องได้ นอกเหนือจากการได้รับสถานะที่อัปเดตแล้วไม่มีการเชื่อมต่อระหว่างผู้สังเกตการณ์และเรื่องที่ลงทะเบียนเพื่อฟัง ผู้สังเกตการณ์ใส่ใจเกี่ยวกับวัตถุของรัฐไม่ใช่เรื่องของตัวเอง กล่าวคือในหลาย ๆ กรณีคอนกรีตเซิร์ฟเวอร์และคอนกรีตมีการเชื่อมโยงเข้าด้วยกัน ในทางตรงกันข้ามเมื่อ concretesubject เรียกฟังก์ชั่นการอัปเดตวัตถุสถานะจะถูกส่งไปยังคอนกรีตบอร์เวอร์และทั้งสองไม่จำเป็นต้องเชื่อมโยง ความสัมพันธ์ระหว่างคอนกรีตเซิร์ฟเวอร์และวัตถุของรัฐลดระดับการพึ่งพาระหว่างผู้สังเกตการณ์และรัฐ (ดูบทความของมาร์ตินฟาวเลอร์สำหรับความแตกต่างเพิ่มเติมในการเชื่อมโยงและการพึ่งพา)
ผสานคลาสนามธรรมและคอนกรีตในระดับ SinglesUbject ในกรณีส่วนใหญ่การใช้คลาสนามธรรมในหัวเรื่องไม่ได้ปรับปรุงความยืดหยุ่นของโปรแกรมและความสามารถในการปรับขนาดดังนั้นการรวมคลาสนามธรรมและคลาสคอนกรีตนี้ทำให้การออกแบบง่ายขึ้น
หลังจากรวมทั้งสองรุ่นพิเศษนี้แผนภาพคลาสที่เรียบง่ายมีดังนี้:
ในโมเดลพิเศษเหล่านี้โครงสร้างคลาสคงที่นั้นง่ายขึ้นอย่างมากและการโต้ตอบระหว่างคลาสก็ง่ายขึ้นเช่นกัน แผนภาพลำดับในเวลานี้มีดังนี้:
คุณสมบัติอีกประการหนึ่งของโหมดความเชี่ยวชาญคือการลบตัวแปรสมาชิกสังเกตการณ์ของคอนกรีต บางครั้งผู้สังเกตการณ์เฉพาะไม่จำเป็นต้องบันทึกสถานะล่าสุดของเรื่อง แต่จำเป็นต้องตรวจสอบสถานะของเรื่องเมื่อมีการอัปเดตสถานะ ตัวอย่างเช่นหากผู้สังเกตการณ์อัปเดตค่าของตัวแปรสมาชิกไปยังเอาต์พุตมาตรฐานเขาสามารถลบการสังเกตการณ์ซึ่งจะช่วยขจัดความสัมพันธ์ระหว่างคอนกรีตบอสเซิร์ฟเวอร์และระดับรัฐ
กฎการตั้งชื่อทั่วไปมากขึ้น
โมเดลคลาสสิกและแม้กระทั่งโมเดลมืออาชีพที่กล่าวถึงข้างต้นคำศัพท์การใช้งานเช่นแนบ, detach และ observer ในขณะที่การใช้งาน Java จำนวนมากใช้พจนานุกรมที่แตกต่างกันรวมถึงการลงทะเบียน, Unregister, ผู้ฟัง ฯลฯ มันก็คุ้มค่าที่จะกล่าวถึงรัฐว่าเป็นคำทั่วไปสำหรับวัตถุทั้งหมดที่ผู้ฟังต้องการตรวจสอบการเปลี่ยนแปลง ชื่อเฉพาะของวัตถุสถานะขึ้นอยู่กับสถานการณ์ที่ใช้ในโหมดผู้สังเกตการณ์ ตัวอย่างเช่นในโหมดผู้สังเกตการณ์ในฉากที่ผู้ฟังฟังเหตุการณ์ที่เกิดขึ้นผู้ฟังที่ลงทะเบียนจะได้รับการแจ้งเตือนเมื่อเหตุการณ์เกิดขึ้น วัตถุสถานะในเวลานี้คือเหตุการณ์นั่นคือไม่ว่าเหตุการณ์จะเกิดขึ้นหรือไม่
ในแอปพลิเคชันจริงการตั้งชื่อของเป้าหมายไม่ค่อยมีหัวเรื่อง ตัวอย่างเช่นสร้างแอพเกี่ยวกับสวนสัตว์ลงทะเบียนผู้ฟังหลายคนเพื่อสังเกตชั้นเรียนสวนสัตว์และรับการแจ้งเตือนเมื่อสัตว์ใหม่เข้าสวนสัตว์ เป้าหมายในกรณีนี้คือคลาสสวนสัตว์ เพื่อให้คำศัพท์สอดคล้องกับโดเมนปัญหาที่กำหนดคำว่า "หัวเรื่อง" จะไม่ถูกนำมาใช้ซึ่งหมายความว่าคลาสสวนสัตว์จะไม่ได้รับการตั้งชื่อ zoosubject
การตั้งชื่อของผู้ฟังมักจะตามมาด้วยคำต่อท้ายของผู้ฟัง ตัวอย่างเช่นผู้ฟังที่กล่าวถึงข้างต้นเพื่อตรวจสอบสัตว์ใหม่จะได้รับการตั้งชื่อว่า AnimaladdedListener ในทำนองเดียวกันการตั้งชื่อของฟังก์ชั่นเช่นการลงทะเบียนการไม่ลงทะเบียนและการแจ้งเตือนมักจะถูกต่อท้ายโดยชื่อผู้ฟังที่เกี่ยวข้อง ตัวอย่างเช่นการลงทะเบียนการไม่ลงทะเบียนและการแจ้งการทำงานของ AnimaladdedListener จะได้รับการตั้งชื่อว่า RegisteranimaladdedListener, UnregisteranimaladdedListener และ NotifyanimaladdedDlisteners ควรสังเกตว่าชื่อฟังก์ชั่นการแจ้งเตือนถูกใช้เพราะฟังก์ชั่นการแจ้งเตือนนั้นจัดการกับผู้ฟังหลายคนแทนที่จะเป็นผู้ฟังคนเดียว
วิธีการตั้งชื่อนี้จะดูยาวนานและโดยปกติแล้ววิชาจะลงทะเบียนผู้ฟังหลายประเภท ตัวอย่างเช่นในตัวอย่างสวนสัตว์ที่กล่าวถึงข้างต้นในสวนสัตว์นอกเหนือจากการลงทะเบียนผู้ฟังใหม่สำหรับการตรวจสอบสัตว์มันยังต้องลงทะเบียนผู้ฟังสัตว์เพื่อลดผู้ฟัง ในเวลานี้จะมีฟังก์ชั่นการลงทะเบียนสองฟังก์ชั่น: (registeranimaladdedListener และ registeranimalremovedListener ด้วยวิธีนี้ประเภทของผู้ฟังจะใช้เป็นตัวคัดเลือกเพื่อระบุประเภทของผู้สังเกตการณ์อีกวิธีหนึ่งคือการสร้างฟังก์ชั่นการลงทะเบียน
ไวยากรณ์สำนวนอื่นคือการใช้กับคำนำหน้าแทนการอัปเดตตัวอย่างเช่นฟังก์ชั่นการอัปเดตมีชื่อ onanimaladded แทน updateanimaladded สถานการณ์นี้เป็นเรื่องธรรมดามากขึ้นเมื่อผู้ฟังได้รับการแจ้งเตือนสำหรับลำดับเช่นการเพิ่มสัตว์ลงในรายการ แต่ไม่ค่อยใช้เพื่ออัปเดตข้อมูลแยกต่างหากเช่นชื่อของสัตว์
ถัดไปบทความนี้จะใช้กฎสัญลักษณ์ของ Java แม้ว่ากฎเชิงสัญลักษณ์จะไม่เปลี่ยนการออกแบบที่แท้จริงและการใช้งานระบบ แต่เป็นหลักการพัฒนาที่สำคัญในการใช้คำศัพท์ที่นักพัฒนาคนอื่นคุ้นเคยดังนั้นคุณต้องคุ้นเคยกับกฎสัญลักษณ์สัญลักษณ์ผู้สังเกตการณ์ใน Java ที่อธิบายไว้ข้างต้น แนวคิดข้างต้นจะอธิบายไว้ด้านล่างโดยใช้ตัวอย่างง่ายๆในสภาพแวดล้อม Java 8
ตัวอย่างง่ายๆ
นอกจากนี้ยังเป็นตัวอย่างของสวนสัตว์ที่กล่าวถึงข้างต้น การใช้อินเทอร์เฟซ API ของ Java8 เพื่อใช้ระบบง่าย ๆ อธิบายหลักการพื้นฐานของรูปแบบผู้สังเกตการณ์ ปัญหาอธิบายเป็น:
สร้างสวนสัตว์ระบบช่วยให้ผู้ใช้ฟังและยกเลิกสถานะของการเพิ่มสัตว์วัตถุใหม่และสร้างผู้ฟังเฉพาะซึ่งรับผิดชอบในการส่งสัญญาณของสัตว์ใหม่
จากการเรียนรู้ก่อนหน้าของรูปแบบผู้สังเกตการณ์เรารู้ว่าการใช้แอปพลิเคชันดังกล่าวเราจำเป็นต้องสร้างคลาส 4 คลาสโดยเฉพาะ:
คลาส Zoo: เช่นธีมในรูปแบบซึ่งรับผิดชอบในการจัดเก็บสัตว์ทั้งหมดในสวนสัตว์และแจ้งผู้ฟังที่ลงทะเบียนทั้งหมดเมื่อสัตว์ใหม่เข้าร่วม
ชั้นสัตว์: แสดงถึงวัตถุสัตว์
คลาส AnimaladdedListener: นั่นคืออินเทอร์เฟซผู้สังเกตการณ์
PrintnameanimaladdedListener: คลาส Observer ที่เฉพาะเจาะจงมีหน้าที่รับผิดชอบในการส่งออกชื่อของสัตว์ที่เพิ่มขึ้นใหม่
ก่อนอื่นเราสร้างคลาสสัตว์ซึ่งเป็นวัตถุ Java อย่างง่ายที่มีตัวแปรสมาชิกชื่อตัวสร้าง, getters และวิธีการตั้งค่า รหัสมีดังนี้:
Animal Class Public {ชื่อสตริงส่วนตัว; สัตว์สาธารณะ (ชื่อสตริง) {this.name = name;} public String getName () {return this.name;} โมฆะสาธารณะ setName (ชื่อสตริง) {this.name = name;}}}ใช้คลาสนี้เพื่อเป็นตัวแทนของวัตถุสัตว์จากนั้นคุณสามารถสร้างอินเทอร์เฟซ AnimalAddedListener:
Public Interface AnimaladdedListener {โมฆะสาธารณะ onanimaladded (สัตว์สัตว์);}สองชั้นแรกนั้นง่ายมากดังนั้นฉันจะไม่แนะนำพวกเขาอย่างละเอียด ถัดไปสร้างคลาส Zoo:
สวนสาธารณะระดับสาธารณะ {รายการส่วนตัว <Anity> สัตว์ = new ArrayList <> (); รายการส่วนตัว <AnityAddedListener> ผู้ฟัง = arrayList ใหม่ <> (); โมฆะสาธารณะ addanimal (สัตว์สัตว์) {// เพิ่มสัตว์ลงในรายการสัตว์ที่มีความแอนิเมชั่น registeranimaladdedListener (AnimalAddedListener Listener) {// เพิ่มผู้ฟังลงในรายการผู้ฟังที่ลงทะเบียนแล้ว listeners.add (ผู้ฟัง);} โมฆะสาธารณะไม่ได้ลงทะเบียนผู้ฟัง NotifyanimaladdedListeners (Animal Animal) {// แจ้งผู้ฟังแต่ละคนในรายชื่อผู้ฟังที่ลงทะเบียนผู้ฟัง steners.listeners.foreach (ผู้ฟัง -> Listener.updateanimaladded (สัตว์));}}}}การเปรียบเทียบนี้ซับซ้อนกว่าสองก่อนหน้า มันมีสองรายการหนึ่งรายการถูกใช้เพื่อเก็บสัตว์ทั้งหมดในสวนสัตว์และอีกรายการหนึ่งใช้เพื่อจัดเก็บผู้ฟังทั้งหมด เนื่องจากวัตถุที่เก็บไว้ในสัตว์และคอลเลกชันผู้ฟังนั้นง่ายบทความนี้เลือก ArrayList สำหรับการจัดเก็บ โครงสร้างข้อมูลเฉพาะของผู้ฟังที่เก็บไว้นั้นขึ้นอยู่กับปัญหา ตัวอย่างเช่นสำหรับปัญหาสวนสัตว์ที่นี่หากผู้ฟังมีความสำคัญคุณควรเลือกโครงสร้างข้อมูลอื่นหรือเขียนอัลกอริทึมการลงทะเบียนของผู้ฟัง
การใช้งานการลงทะเบียนและการลบเป็นทั้งวิธีการแทนอย่างง่าย: ผู้ฟังแต่ละคนจะถูกเพิ่มหรือลบออกจากรายการการฟังของผู้ฟังเป็นพารามิเตอร์ การใช้งานฟังก์ชั่นการแจ้งเตือนนั้นปิดเล็กน้อยจากรูปแบบมาตรฐานของรูปแบบผู้สังเกตการณ์ มันรวมถึงพารามิเตอร์อินพุต: สัตว์ที่เพิ่มขึ้นใหม่เพื่อให้ฟังก์ชั่นการแจ้งเตือนสามารถผ่านการอ้างอิงสัตว์ที่เพิ่มขึ้นใหม่ไปยังผู้ฟัง ใช้ฟังก์ชั่น foreach ของสตรีม API เพื่อสำรวจผู้ฟังและดำเนินการฟังก์ชั่น TheonAnimaladded ในผู้ฟังแต่ละคน
ในฟังก์ชั่น addanimal วัตถุและผู้ฟังที่เพิ่มเข้ามาใหม่จะถูกเพิ่มเข้าไปในรายการที่เกี่ยวข้อง หากความซับซ้อนของกระบวนการแจ้งเตือนไม่ได้นำมาพิจารณาตรรกะนี้ควรรวมอยู่ในวิธีการโทรที่สะดวก คุณจะต้องผ่านการอ้างอิงถึงวัตถุสัตว์ที่เพิ่มขึ้นใหม่ นี่คือเหตุผลที่การใช้งานเชิงตรรกะของผู้ฟังการแจ้งเตือนถูกห่อหุ้มในฟังก์ชั่น NotifyanimaladdedListeners ซึ่งถูกกล่าวถึงในการดำเนินการของ Addanimal
นอกเหนือจากปัญหาเชิงตรรกะของฟังก์ชั่นการแจ้งเตือนแล้วยังมีความจำเป็นที่จะต้องเน้นประเด็นการโต้เถียงเกี่ยวกับการมองเห็นฟังก์ชั่นการแจ้งเตือน ในโมเดลผู้สังเกตการณ์แบบคลาสสิกดังที่ GOF กล่าวไว้ในหน้า 301 ของรูปแบบการออกแบบหนังสือฟังก์ชั่นการแจ้งเตือนเป็นแบบสาธารณะ แต่ถึงแม้ว่าจะใช้ในรูปแบบคลาสสิก แต่นี่ไม่ได้หมายความว่ามันจะต้องเปิดเผยต่อสาธารณะ การเลือกการมองเห็นควรขึ้นอยู่กับแอปพลิเคชัน ตัวอย่างเช่นในตัวอย่างสวนสัตว์ของบทความนี้ฟังก์ชั่นการแจ้งเตือนเป็นประเภทที่ได้รับการปกป้องและไม่ต้องการให้แต่ละวัตถุเริ่มการแจ้งเตือนผู้สังเกตการณ์ที่ลงทะเบียน จำเป็นต้องตรวจสอบให้แน่ใจว่าวัตถุสามารถสืบทอดฟังก์ชั่นจากคลาสแม่ แน่นอนว่านี่ไม่ใช่กรณีที่แน่นอน มีความจำเป็นที่จะต้องพิจารณาว่าคลาสใดที่สามารถเปิดใช้งานฟังก์ชั่นการแจ้งเตือนจากนั้นพิจารณาการมองเห็นของฟังก์ชั่น
ถัดไปคุณจะต้องใช้คลาส PrintnameanimaladdedListener คลาสนี้ใช้วิธี System.out.println เพื่อส่งออกชื่อของสัตว์ใหม่ รหัสเฉพาะมีดังนี้:
Public Class PrintnameanimaladdedListener ใช้ AnimaladdedListener {@Overridepublic เป็นโมฆะ updateanimaladded (สัตว์สัตว์) {// พิมพ์ชื่อของ imentyystems.out.out.println ("เพิ่มสัตว์ใหม่ที่มีชื่อ 'ในที่สุดเราจำเป็นต้องใช้ฟังก์ชั่นหลักที่ขับเคลื่อนแอปพลิเคชัน:
ชั้นเรียนสาธารณะหลัก {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// สร้างสวนสัตว์เพื่อจัดเก็บสัตว์ Zoo = สวนสัตว์ใหม่ (); // ลงทะเบียนผู้ฟังที่จะได้รับแจ้งเมื่อสัตว์ถูกเพิ่ม zoo.registeranimaladdedListener สัตว์ ("เสือ"));}}ฟังก์ชั่นหลักเพียงแค่สร้างวัตถุสวนสัตว์ลงทะเบียนผู้ฟังที่ส่งออกชื่อสัตว์และสร้างวัตถุสัตว์ใหม่เพื่อกระตุ้นผู้ฟังที่ลงทะเบียน ผลลัพธ์สุดท้ายคือ:
เพิ่มสัตว์ใหม่ด้วยชื่อ 'Tiger'
เพิ่มผู้ฟัง
ข้อดีของโหมดผู้สังเกตการณ์จะปรากฏขึ้นอย่างสมบูรณ์เมื่อผู้ฟังได้รับการสร้างใหม่และเพิ่มเข้าไปในวัตถุ ตัวอย่างเช่นหากคุณต้องการเพิ่มผู้ฟังที่คำนวณจำนวนสัตว์ทั้งหมดในสวนสัตว์คุณเพียงแค่ต้องสร้างคลาสผู้ฟังที่เฉพาะเจาะจงและลงทะเบียนกับคลาสสวนสัตว์โดยไม่ต้องปรับเปลี่ยนคลาส Zoo การเพิ่มรหัสการนับจำนวนมาก
การนับระดับสาธารณะนับ AnimaladdedListener ใช้ AnimalAddedListener {สัตว์ inttatic ส่วนตัว int private intateredCount = 0; @Overridepublic เป็นโมฆะ updateanimaladded (สัตว์สัตว์) {// เพิ่มจำนวนสัตว์ out.out.println ("สัตว์ทั้งหมดเพิ่ม:"ฟังก์ชั่นหลักที่ได้รับการแก้ไขมีดังนี้:
คลาสสาธารณะหลัก {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// สร้างสวนสัตว์เพื่อจัดเก็บสัตว์ Zoo = สวนสัตว์ใหม่ (); // ลงทะเบียนผู้ฟังที่จะได้รับการแจ้งเตือนเมื่อสัตว์ถูกเพิ่ม zoo.registeranimaldedListener แจ้ง Listenerszoo.addanimal (สัตว์ใหม่ ("Tiger")); Zoo.Addanimal (สัตว์ใหม่ ("Lion")); Zoo.Addanimal (สัตว์ใหม่ ("Bear"));}}ผลลัพธ์ผลลัพธ์คือ:
เพิ่มสัตว์ใหม่ที่มีชื่อ 'Tiger' สัตว์ 'เพิ่ม: 1 เพิ่มสัตว์ใหม่ที่มีชื่อ' Lion 'สัตว์' เพิ่ม: 2 เพิ่มสัตว์ใหม่พร้อมชื่อ 'Bear' สัตว์รวม: 3 เพิ่ม: 3
ผู้ใช้สามารถสร้างผู้ฟังได้หากแก้ไขรหัสการลงทะเบียนผู้ฟังเท่านั้น ความสามารถในการปรับขนาดนี้ส่วนใหญ่เป็นเพราะตัวแบบเกี่ยวข้องกับอินเทอร์เฟซ Observer แทนที่จะเชื่อมโยงโดยตรงกับคอนกรีต ตราบใดที่อินเทอร์เฟซยังไม่ได้รับการแก้ไขไม่จำเป็นต้องแก้ไขหัวเรื่องของอินเทอร์เฟซ
คลาสภายในที่ไม่ระบุชื่อฟังก์ชั่นแลมบ์ดาและการลงทะเบียนผู้ฟัง
การปรับปรุงที่สำคัญใน Java 8 คือการเพิ่มคุณสมบัติการทำงานเช่นการเพิ่มฟังก์ชั่นแลมบ์ดา ก่อนที่จะแนะนำฟังก์ชั่นแลมบ์ดา Java ให้ฟังก์ชั่นที่คล้ายกันผ่านคลาสภายในที่ไม่ระบุชื่อซึ่งยังคงใช้ในแอปพลิเคชันที่มีอยู่จำนวนมาก ในโหมดผู้สังเกตการณ์ผู้ฟังใหม่สามารถสร้างได้ตลอดเวลาโดยไม่ต้องสร้างคลาส Observer เฉพาะ ตัวอย่างเช่นคลาส printnameanimaladdedlistener สามารถนำไปใช้ในฟังก์ชั่นหลักที่มีคลาสภายในที่ไม่ระบุชื่อ รหัสการใช้งานเฉพาะมีดังนี้:
ชั้นเรียนสาธารณะหลัก {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {// สร้างสวนสัตว์เพื่อจัดเก็บสัตว์ Zoo = สวนสัตว์ใหม่ (); // ลงทะเบียนผู้ฟังที่จะได้รับการแจ้งเตือนเมื่อสัตว์ถูกเพิ่ม registeranimaladdedListener Animalsystem.out.println ("เพิ่มสัตว์ใหม่ที่มีชื่อ '" + iment.getName () + "'");}}); // เพิ่มสัตว์แจ้งให้ผู้ฟังที่ลงทะเบียน listenerszoo.addanimal (สัตว์ใหม่ ("Tiger"));}}ในทำนองเดียวกันฟังก์ชั่นแลมบ์ดาสามารถใช้เพื่อทำงานให้เสร็จสมบูรณ์:
คลาสสาธารณะหลัก {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// สร้างสวนสัตว์เพื่อจัดเก็บสัตว์ Zoo = สวนสัตว์ใหม่ (); // ลงทะเบียนผู้ฟังที่จะได้รับการแจ้งเตือนเมื่อสัตว์ถูกเพิ่ม Zoo.registeranimaladdedListener listenerszoo.addanimal (สัตว์ใหม่ ("tiger"));}}ควรสังเกตว่าฟังก์ชั่นแลมบ์ดาเหมาะสำหรับสถานการณ์ที่มีเพียงฟังก์ชั่นเดียวในอินเทอร์เฟซผู้ฟัง แม้ว่าข้อกำหนดนี้ดูเหมือนจะเข้มงวด แต่ผู้ฟังหลายคนเป็นฟังก์ชั่นเดียวเช่น AnimaladdedListener ในตัวอย่าง หากอินเทอร์เฟซมีหลายฟังก์ชั่นคุณสามารถเลือกใช้คลาสภายในที่ไม่ระบุชื่อ
มีปัญหาดังกล่าวเกี่ยวกับการลงทะเบียนโดยนัยของผู้ฟังที่สร้างขึ้น: เนื่องจากวัตถุถูกสร้างขึ้นภายในขอบเขตของการโทรลงทะเบียนจึงเป็นไปไม่ได้ที่จะเก็บข้อมูลอ้างอิงไปยังผู้ฟังเฉพาะ ซึ่งหมายความว่าผู้ฟังที่ลงทะเบียนผ่านฟังก์ชั่นแลมบ์ดาหรือคลาสภายในที่ไม่ระบุชื่อไม่สามารถเพิกถอนได้เนื่องจากฟังก์ชั่นการเพิกถอนต้องมีการอ้างอิงถึงผู้ฟังที่ลงทะเบียน วิธีง่ายๆในการแก้ปัญหานี้คือการส่งคืนการอ้างอิงไปยังผู้ฟังที่ลงทะเบียนในฟังก์ชัน registeranimaladdedListener ด้วยวิธีนี้คุณสามารถยกเลิกการลงทะเบียนผู้ฟังที่สร้างด้วยฟังก์ชั่นแลมบ์ดาหรือคลาสภายในที่ไม่ระบุชื่อ รหัสวิธีที่ได้รับการปรับปรุงมีดังนี้:
Public AnimaladdedListener RegisteranimaladdedListener (AnimaladdedListener Listener) {// เพิ่มผู้ฟังลงในรายชื่อผู้ฟังที่ลงทะเบียนแล้ว listeners.add (ผู้ฟัง); ส่งคืนผู้ฟัง;}รหัสไคลเอนต์สำหรับการโต้ตอบฟังก์ชั่นที่ออกแบบใหม่มีดังนี้:
คลาสสาธารณะหลัก {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// สร้างสวนสัตว์เพื่อเก็บสัตว์ Zoo Zoo = สวนสัตว์ใหม่ (); // ลงทะเบียนผู้ฟังที่จะได้รับการแจ้งเตือนเมื่อสัตว์ถูกเพิ่ม AnimaladdedListener ผู้ฟัง = zoo.registeranimaldedListener "" ")); // เพิ่มสัตว์แจ้งให้ผู้ฟังที่ลงทะเบียน Zoo.addanimal (สัตว์ใหม่ (" Tiger ")); // ยกเลิกการลงทะเบียนผู้ฟัง unregisteranimaladdedListener (ผู้ฟัง); // เพิ่มสัตว์อื่นซึ่งจะไม่พิมพ์ชื่อผลลัพธ์ผลลัพธ์ในเวลานี้จะเพิ่มสัตว์ใหม่ที่มีชื่อ 'Tiger' เท่านั้นเนื่องจากผู้ฟังถูกยกเลิกก่อนที่จะเพิ่มสัตว์ที่สอง:
เพิ่มสัตว์ใหม่ด้วยชื่อ 'Tiger'
หากมีการใช้โซลูชันที่ซับซ้อนมากขึ้นฟังก์ชั่นการลงทะเบียนยังสามารถส่งคืนคลาสผู้รับเพื่อให้ผู้ฟังที่ไม่ได้ลงทะเบียนถูกเรียกเช่น:
Public Class AnimaladdedListenerreceipt {Private Final AnimaleddedListener ผู้ฟัง; AnimaladdedListenerreceipt (AnimaladdedListener ผู้ฟัง) {this.listener = ผู้ฟังใบเสร็จรับเงินจะถูกใช้เป็นค่าส่งคืนของฟังก์ชันการลงทะเบียนและพารามิเตอร์อินพุตของฟังก์ชันการลงทะเบียนจะถูกยกเลิก ในเวลานี้การใช้งานสวนสัตว์มีดังนี้:
ระดับสาธารณะ zoousingReceipt {// ... คุณลักษณะที่มีอยู่และตัวสร้าง ... สัตว์สาธารณะ imenteddlistenerreceipt registeranimaladdedlistener (AnimaladdedListener ผู้ฟัง) {// เพิ่มผู้ฟังลงในรายการผู้ฟังที่ลงทะเบียน UnregisteranimaladdedListener (AnimaladdedListenerreceipt Reception) {// ลบผู้ฟังออกจากรายชื่อผู้ฟังที่ลงทะเบียนแล้ว listeners.remove (receipt.getListener ());} // ... วิธีการแจ้งเตือนที่มีอยู่ ... }}}}}}กลไกการใช้งานที่ได้รับที่อธิบายไว้ข้างต้นช่วยให้การจัดเก็บข้อมูลสำหรับการโทรไปยังผู้ฟังเมื่อเพิกถอนนั่นคือถ้าอัลกอริทึมการลงทะเบียนการเพิกถอนขึ้นอยู่กับสถานะของผู้ฟังเมื่อผู้ลงทะเบียนผู้ฟังสถานะนี้จะถูกบันทึกไว้ หากการลงทะเบียนการเพิกถอนต้องมีการอ้างอิงถึงผู้ฟังที่ลงทะเบียนก่อนหน้านี้เท่านั้นเทคโนโลยีการรับจะปรากฏว่ามีปัญหาและไม่แนะนำ
นอกเหนือจากผู้ฟังที่เฉพาะเจาะจงโดยเฉพาะอย่างยิ่งวิธีที่พบบ่อยที่สุดในการลงทะเบียนผู้ฟังคือผ่านฟังก์ชั่นแลมบ์ดาหรือผ่านชั้นเรียนภายในที่ไม่ระบุชื่อ แน่นอนว่ามีข้อยกเว้นนั่นคือคลาสที่มีหัวเรื่องใช้อินเทอร์เฟซผู้สังเกตการณ์และลงทะเบียนผู้ฟังที่เรียกเป้าหมายอ้างอิง กรณีที่แสดงในรหัสต่อไปนี้:
ชั้นเรียนสาธารณะ Zoocontainer ใช้ AnimaladdedListener {Zoo Zoo ส่วนตัว = สวนสัตว์ใหม่ (); Zoocontainer สาธารณะ () {// ลงทะเบียนวัตถุนี้เป็นผู้ฟัง zoo.zoo.registeranimaladdedListener (this);} zoo public Zoo getzoo {system.out.println ("เพิ่มสัตว์ด้วยชื่อ '" + iment.getName () + "'");} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// สร้าง Zoo Adnerzontainer (end) adhiner.getainer. // สัตว์ ("เสือ"));}}วิธีการนี้เหมาะสำหรับกรณีง่ายๆเท่านั้นและรหัสดูเหมือนจะไม่เป็นมืออาชีพและยังคงเป็นที่นิยมมากกับนักพัฒนา Java ที่ทันสมัยดังนั้นจึงจำเป็นต้องเข้าใจว่าตัวอย่างนี้ทำงานอย่างไร เนื่องจาก Zoocontainer ใช้อินเทอร์เฟซ AnimalAddedListener ดังนั้นอินสแตนซ์ (หรือวัตถุ) ของ Zoocontainer จึงสามารถลงทะเบียนเป็น AnimaladdedListener ในคลาส zoocontainer การอ้างอิงนี้แสดงถึงอินสแตนซ์ของวัตถุปัจจุบันคือ zoocontainer และสามารถใช้เป็น AnimaladdedListener
โดยทั่วไปไม่จำเป็นต้องใช้คลาสคอนเทนเนอร์ทั้งหมดในการใช้งานฟังก์ชั่นดังกล่าวและคลาสคอนเทนเนอร์ที่ใช้อินเตอร์เฟสผู้ฟังสามารถเรียกใช้ฟังก์ชันการลงทะเบียนเรื่อง แต่เพียงแค่ผ่านการอ้างอิงไปยังฟังก์ชั่นการลงทะเบียนเป็นวัตถุฟัง ในบทต่อไปนี้คำถามที่พบบ่อยและโซลูชั่นสำหรับสภาพแวดล้อมแบบมัลติเธรดจะได้รับการแนะนำ
ONEAPM ให้บริการโซลูชั่นประสิทธิภาพการใช้งาน Java Application end-to-end เราสนับสนุนเฟรมเวิร์ก Java ทั่วไปทั้งหมดและเซิร์ฟเวอร์แอปพลิเคชันเพื่อช่วยให้คุณค้นพบคอขวดของระบบอย่างรวดเร็วและค้นหาสาเหตุของความผิดปกติ การปรับใช้ในระดับนาทีและประสบการณ์ทันทีการตรวจสอบ Java ไม่เคยง่ายกว่านี้มาก่อน หากต้องการอ่านบทความทางเทคนิคเพิ่มเติมโปรดเยี่ยมชมบล็อกเทคโนโลยีอย่างเป็นทางการของ OneAPM
เนื้อหาข้างต้นแนะนำเนื้อหาที่เกี่ยวข้องของการใช้ Java 8 เพื่อใช้โหมดผู้สังเกตการณ์ (ตอนที่ 1) บทความถัดไปแนะนำวิธีการใช้ Java 8 เพื่อใช้โหมดผู้สังเกตการณ์ (ตอนที่ 2) เพื่อนที่สนใจจะเรียนรู้ต่อไปโดยหวังว่ามันจะเป็นประโยชน์กับทุกคน!