หนังสือของ Dr. Yan Hong เรื่อง "JAVA and Patterns" เริ่มต้นด้วยคำอธิบายเกี่ยวกับรูปแบบผู้เยี่ยมชม:
รูปแบบผู้เยี่ยมชมคือรูปแบบพฤติกรรมของวัตถุ วัตถุประสงค์ของรูปแบบผู้เยี่ยมชมคือการสรุปการดำเนินการบางอย่างที่ใช้กับองค์ประกอบโครงสร้างข้อมูลบางอย่าง เมื่อจำเป็นต้องแก้ไขการดำเนินการเหล่านี้ โครงสร้างข้อมูลที่ยอมรับการดำเนินการนี้จะยังคงไม่เปลี่ยนแปลง
แนวคิดในการจัดส่ง
ประเภทเมื่อมีการประกาศตัวแปรเรียกว่าประเภทคงที่ของตัวแปร (ประเภทคงที่) และบางคนเรียกประเภทคงที่ว่าเป็นประเภทที่ชัดเจน (ประเภทที่ปรากฏ) และประเภทจริงของวัตถุที่อ้างอิงโดยตัวแปรก็เรียกว่า ชนิดที่แท้จริงของตัวแปร (Actual Type) ตัวอย่างเช่น:
คัดลอกรหัสรหัสดังต่อไปนี้:
รายการรายการ = null;
รายการ = ArrayList ใหม่ ();
มีการประกาศรายการตัวแปร ประเภทคงที่ (หรือเรียกว่าประเภทที่ชัดเจน) คือรายการ และประเภทจริงคือ ArrayList
การเลือกวิธีการตามประเภทของออบเจ็กต์ที่จัดส่งจะแบ่งออกเป็นสองประเภท ได้แก่ การจัดส่งแบบคงที่และการจัดส่งแบบไดนามิก
การจัดส่งแบบคงที่จะเกิดขึ้นในเวลาคอมไพล์ และการจัดส่งจะเกิดขึ้นตามข้อมูลประเภทคงที่ การจัดส่งแบบคงที่ไม่ใช่เรื่องแปลกสำหรับเรา วิธีการโอเวอร์โหลดคือการจัดส่งแบบคงที่
Dynamic Dispatch เกิดขึ้นระหว่างรันไทม์ และ Dynamic Dispatch จะแทนที่วิธีการแบบไดนามิก
การจัดส่งแบบคงที่
Java รองรับการจัดส่งแบบคงที่ผ่านวิธีการโอเวอร์โหลด โดยใช้เรื่องราวของ Mozi ขี่ม้าเป็นตัวอย่าง Mozi สามารถขี่ม้าขาวหรือม้าดำได้ แผนภาพชั้นเรียนของ Mozi กับม้าขาว ม้าดำ และม้า มีดังนี้:
ในระบบนี้ Mozi จะแสดงโดยคลาส Mozi รหัสจะเป็นดังนี้:
Mozi คลาสสาธารณะ {
การนั่งเป็นโมฆะสาธารณะ (Horse h){
System.out.println("ขี่ม้า");
-
การนั่งเป็นโมฆะสาธารณะ (WhiteHorse wh) {
System.out.println("ขี่ม้าขาว");
-
การนั่งเป็นโมฆะสาธารณะ (BlackHorse bh) {
System.out.println("ขี่ม้าดำ");
-
โมฆะสาธารณะคงหลัก (สตริง [] args) {
ม้า wh = ใหม่ WhiteHorse();
ม้า bh = BlackHorse ใหม่ ();
Mozi mozi = ใหม่ Mozi();
mozi.ride(WH);
mozi.ride(bh);
-
-
แน่นอนว่าเมธอด ride() ของคลาส Mozi มีมากเกินไปจากสามวิธี ทั้งสามวิธีนี้ยอมรับพารามิเตอร์ของ Horse, WhiteHorse, BlackHorse และประเภทอื่นๆ ตามลำดับ
แล้วโปรแกรมจะพิมพ์ผลลัพธ์อะไรออกมาเมื่อทำงาน? ผลลัพธ์ก็คือโปรแกรมจะพิมพ์คำว่า "หลังม้า" สองบรรทัดที่เหมือนกัน กล่าวอีกนัยหนึ่ง Mozi ค้นพบว่าสิ่งที่เขาขี่ม้ามีเพียงม้าเท่านั้น
ทำไม การเรียกเมธอด Ride() สองครั้งผ่านพารามิเตอร์ที่แตกต่างกัน คือ wh และ bh แม้ว่าจะมีประเภทจริงที่แตกต่างกัน แต่ประเภทคงที่ก็เหมือนกันทั้งหมด ซึ่งก็คือประเภทม้า
การจัดส่งวิธีการโอเวอร์โหลดจะขึ้นอยู่กับประเภทคงที่ และกระบวนการจัดส่งนี้จะเสร็จสมบูรณ์ในเวลารวบรวม
การส่งแบบไดนามิก
Java รองรับการจัดส่งแบบไดนามิกผ่านการแทนที่วิธีการ โดยใช้เรื่องม้ากินหญ้าเป็นตัวอย่าง มีโค้ดดังนี้
คัดลอกรหัสรหัสดังต่อไปนี้:
ม้าระดับสาธารณะ {
โมฆะสาธารณะกิน () {
System.out.println("ม้ากินหญ้า");
-
-
คัดลอกรหัสรหัสดังต่อไปนี้:
คลาสสาธารณะ BlackHorse ขยาย Horse {
@แทนที่
โมฆะสาธารณะกิน () {
System.out.println("ม้ามืดกินหญ้า");
-
-
คัดลอกรหัสรหัสดังต่อไปนี้:
ลูกค้าคลาสสาธารณะ {
โมฆะสาธารณะคงหลัก (สตริง [] args) {
ม้า h = BlackHorse ใหม่ ();
ความร้อน();
-
-
ประเภทคงที่ของตัวแปร h คือ Horse และประเภทจริงคือ BlackHorse หากเมธอด eat() ในบรรทัดสุดท้ายด้านบนเรียกเมธอด eat() ของคลาส BlackHorse สิ่งที่พิมพ์ไว้ด้านบนคือ "Black Horse Eating Grass" ตรงกันข้าม ถ้าเมธอด eat() ด้านบนเรียก eat( ) วิธีการของคลาส Horse แล้วสิ่งที่พิมพ์คือ "ม้ากินหญ้า"
ดังนั้นแก่นของปัญหาคือคอมไพเลอร์ Java ไม่ทราบว่าโค้ดใดที่จะถูกดำเนินการในระหว่างการคอมไพล์ เนื่องจากคอมไพเลอร์รู้เฉพาะประเภทคงที่ของอ็อบเจ็กต์ แต่ไม่ทราบประเภทที่แท้จริงของอ็อบเจ็กต์และวิธีการ การเรียกจะขึ้นอยู่กับประเภท Real ของออบเจ็กต์ ไม่ใช่ประเภทคงที่ ด้วยวิธีนี้ เมธอด eat() ในบรรทัดสุดท้ายด้านบนจะเรียกเมธอด eat() ของคลาส BlackHorse และพิมพ์ว่า "ม้าดำกินหญ้า"
ประเภทของการจัดส่ง
วัตถุที่เป็นของเมธอดเรียกว่าตัวรับของเมธอดและพารามิเตอร์ของเมธอดเรียกรวมกันว่าปริมาตรของเมธอด ตัวอย่างเช่น รหัสการคัดลอกของคลาสการทดสอบในตัวอย่างด้านล่างจะเป็นดังนี้:
การทดสอบชั้นเรียนสาธารณะ {
การพิมพ์โมฆะสาธารณะ (String str) {
System.out.println(str);
-
-
ในคลาสข้างต้น เมธอด print() เป็นของอ็อบเจ็กต์ Test ดังนั้นตัวรับของมันจึงเป็นอ็อบเจ็กต์ Test เช่นกัน เมธอด print() มีพารามิเตอร์ที่เรียกว่า str และประเภทของมันคือ String
ขึ้นอยู่กับจำนวนประเภทของการจัดส่งตามปริมาณ ภาษาเชิงวัตถุสามารถแบ่งออกเป็นภาษาการจัดส่งเดี่ยว (Uni-Dispatch) และภาษาการจัดส่งหลายรายการ (Multi-Dispatch) ภาษาการจัดส่งแบบเดี่ยวเลือกวิธีการตามประเภทของอินสแตนซ์หนึ่งรายการ ในขณะที่ภาษาการจัดส่งหลายรายการเลือกวิธีการตามประเภทของอินสแตนซ์มากกว่าหนึ่งรายการ
ทั้ง C++ และ Java เป็นภาษาแบบ single-dispatch และตัวอย่างของภาษาแบบ multi-dispatch ได้แก่ CLOS และ Cecil ตามความแตกต่างนี้ Java เป็นภาษาการจัดส่งเดี่ยวแบบไดนามิก เนื่องจากการจัดส่งแบบไดนามิกของภาษานี้คำนึงถึงประเภทของตัวรับเมธอดเท่านั้น และเป็นภาษาแบบส่งหลายรายการแบบคงที่ เนื่องจากภาษานี้ส่งวิธีการโอเวอร์โหลด ประเภทของตัวรับของวิธีการและประเภทของพารามิเตอร์ทั้งหมดของวิธีการจะถูกนำมาพิจารณาด้วย
ในภาษาที่รองรับการจัดส่งครั้งเดียวแบบไดนามิก มีสองเงื่อนไขที่กำหนดว่าการดำเนินการใดที่คำขอจะเรียกใช้: เงื่อนไขหนึ่งคือชื่อของคำขอ และประเภทที่แท้จริงของผู้รับ การจัดส่งครั้งเดียวจำกัดกระบวนการเลือกวิธีการเพื่อให้สามารถพิจารณาได้เพียงอินสแตนซ์เดียวเท่านั้น ซึ่งโดยปกติจะเป็นผู้รับวิธีการ ในภาษา Java หากดำเนินการกับอ็อบเจ็กต์ที่ไม่ทราบประเภท การทดสอบประเภทจริงของอ็อบเจ็กต์จะเกิดขึ้นเพียงครั้งเดียว นี่คือคุณลักษณะของการจัดส่งครั้งเดียวแบบไดนามิก
จัดส่งสองครั้ง
วิธีการตัดสินใจรันโค้ดที่แตกต่างกันตามประเภทของตัวแปรสองตัว นี่คือ "การส่งสองครั้ง" ภาษา Java ไม่รองรับการจ่ายหลายรายการแบบไดนามิก ซึ่งหมายความว่า Java ไม่รองรับการจ่ายสองครั้งแบบไดนามิก แต่ด้วยการใช้รูปแบบการออกแบบ การส่งสองครั้งแบบไดนามิกยังสามารถนำไปใช้ในภาษา Java ได้
ใน Java การจัดส่งสองครั้งสามารถทำได้ผ่านการเรียกเมธอดสองครั้ง แผนภาพคลาสมีดังนี้:
ในภาพมีวัตถุสองชิ้น ชิ้นทางด้านซ้ายเรียกว่าตะวันตก และชิ้นทางด้านขวาเรียกว่าตะวันออก ตอนนี้วัตถุ West เรียกเมธอด goEast() ของวัตถุ East ก่อน โดยส่งผ่านตัวมันเอง เมื่อออบเจ็กต์ East ถูกเรียก มันจะรู้ทันทีว่าใครคือผู้โทรตามพารามิเตอร์ที่ส่งเข้ามา ดังนั้นเมธอด goWest() ของออบเจ็กต์ "ผู้โทร" จึงจะถูกเรียกตามลำดับ ผ่านการเรียกสองครั้ง การควบคุมโปรแกรมจะถูกส่งไปยังอ็อบเจ็กต์สองตัวตามลำดับ แผนภาพลำดับมีดังนี้:
ด้วยวิธีนี้ มีการเรียกเมธอดสองแบบ ตัวควบคุมโปรแกรมถูกส่งผ่านระหว่างวัตถุทั้งสอง ขั้นแรก มันจะถูกส่งผ่านจากวัตถุตะวันตกไปยังวัตถุตะวันออก จากนั้นจะถูกส่งกลับไปยังวัตถุตะวันตก
แต่แค่ส่งบอลกลับไม่ได้ช่วยแก้ปัญหาการกระจายตัวซ้ำซ้อน กุญแจสำคัญคือวิธีใช้การเรียกสองครั้งนี้และฟังก์ชันการจ่ายครั้งเดียวแบบไดนามิกของภาษา Java เพื่อทริกเกอร์การจ่ายครั้งเดียวสองครั้งในระหว่างกระบวนการส่งผ่านนี้
การจัดส่งเดี่ยวแบบไดนามิกในภาษา Java เกิดขึ้นเมื่อคลาสย่อยแทนที่เมธอดของคลาสพาเรนต์ กล่าวอีกนัยหนึ่ง ทั้งตะวันตกและตะวันออกจะต้องอยู่ในลำดับชั้นประเภทของตนเอง ดังที่แสดงด้านล่าง:
ซอร์สโค้ด
รหัสการคัดลอกคลาสตะวันตกมีดังนี้:
คลาสนามธรรมสาธารณะตะวันตก {
โมฆะนามธรรมสาธารณะ goWest1 (SubEast1 ตะวันออก);
โมฆะนามธรรมสาธารณะ goWest2 (SubEast2 ตะวันออก);
-
รหัสคัดลอกคลาส SubWest1 เป็นดังนี้:
คลาสสาธารณะ SubWest1 ขยายไปทางตะวันตก {
@แทนที่
โมฆะสาธารณะ goWest1 (SubEast1 ตะวันออก) {
System.out.println("SubWest1 + " + east.myName1());
-
@แทนที่
โมฆะสาธารณะ goWest2 (SubEast2 ตะวันออก) {
System.out.println("SubWest1 + " + east.myName2());
-
-
ซับเวสต์คลาส 2
คัดลอกรหัสรหัสดังต่อไปนี้:
คลาสสาธารณะ SubWest2 ขยายไปทางตะวันตก {
@แทนที่
โมฆะสาธารณะ goWest1 (SubEast1 ตะวันออก) {
System.out.println("SubWest2 + " + east.myName1());
-
@แทนที่
โมฆะสาธารณะ goWest2 (SubEast2 ตะวันออก) {
System.out.println("SubWest2 + " + east.myName2());
-
-
รหัสการคัดลอกคลาสตะวันออกมีดังนี้:
คลาสนามธรรมสาธารณะตะวันออก {
โมฆะนามธรรมสาธารณะ goEast (ตะวันตกตะวันตก);
-
รหัสคัดลอกคลาส SubEast1 เป็นดังนี้:
คลาสสาธารณะ SubEast1 ขยายไปทางตะวันออก{
@แทนที่
โมฆะสาธารณะ goEast (ตะวันตกตะวันตก) {
west.goWest1(นี้);
-
สตริงสาธารณะ myName1(){
กลับ "SubEast1";
-
-
รหัสคัดลอกคลาส SubEast2 เป็นดังนี้:
คลาสสาธารณะ SubEast2 ขยายไปทางตะวันออก{
@แทนที่
โมฆะสาธารณะ goEast (ตะวันตกตะวันตก) {
west.goWest2(นี้);
-
สตริงสาธารณะ myName2(){
กลับ "SubEast2";
-
-
รหัสการคัดลอกคลาสไคลเอนต์เป็นดังนี้:
ลูกค้าคลาสสาธารณะ {
โมฆะสาธารณะคงหลัก (สตริง [] args) {
//การรวมกัน 1
ตะวันออก ตะวันออก = ใหม่ SubEast1();
ตะวันตก ตะวันตก = ใหม่ SubWest1();
east.goEast (ตะวันตก);
//การรวมกัน 2
ตะวันออก = ใหม่ SubEast1();
ตะวันตก = ใหม่ SubWest2();
east.goEast (ตะวันตก);
-
-
ผลลัพธ์การทำงานมีดังนี้
ซับเวสต์1 + ซับอีสต์1
ซับเวสต์2 + ซับอีสต์1
เมื่อระบบกำลังทำงาน ออบเจ็กต์ SubWest1 และ SubEast1 จะถูกสร้างขึ้นในครั้งแรก จากนั้นไคลเอ็นต์จะเรียกเมธอด goEast() ของ SubEast1 และส่งผ่านในออบเจ็กต์ SubWest1 เนื่องจากออบเจ็กต์ SubEast1 จะแทนที่เมธอด goEast() ของซูเปอร์คลาส East จึงมีการส่งคำสั่งไดนามิกเดี่ยวเกิดขึ้นในเวลานี้ เมื่อออบเจ็กต์ SubEast1 ได้รับการเรียก มันจะรับออบเจ็กต์ SubWest1 จากพารามิเตอร์ ดังนั้นจึงเรียกเมธอด goWest1() ของออบเจ็กต์นี้ทันทีและส่งผ่านตัวมันเองเข้าไป เนื่องจากออบเจ็กต์ SubEast1 มีสิทธิ์เลือกออบเจ็กต์ที่จะเรียก จึงมีการดำเนินการจัดส่งเมธอดแบบไดนามิกอื่นในเวลานี้
ในขณะนี้ วัตถุ SubWest1 ได้รับวัตถุ SubEast1 ด้วยการเรียกเมธอด myName1() ของออบเจ็กต์นี้ คุณสามารถพิมพ์ชื่อของคุณเองและชื่อของออบเจ็กต์ SubEast ได้ แผนภาพลำดับมีดังนี้:
เนื่องจากหนึ่งในสองชื่อนี้มาจากลำดับชั้นตะวันออกและอีกชื่อมาจากลำดับชั้นตะวันตก การรวมกันจึงถูกกำหนดแบบไดนามิก นี่คือกลไกการดำเนินการของการจัดส่งแบบคู่แบบไดนามิก
โครงสร้างของรูปแบบผู้มาเยือน
รูปแบบผู้เยี่ยมชมเหมาะสำหรับระบบที่มีโครงสร้างข้อมูลที่ไม่ได้กำหนดไว้ค่อนข้างมาก โดยจะแยกการเชื่อมต่อระหว่างโครงสร้างข้อมูลและการดำเนินการที่กระทำกับโครงสร้าง ทำให้ชุดการดำเนินการสามารถพัฒนาได้อย่างอิสระ แผนภาพแบบง่ายของรูปแบบผู้เยี่ยมชมแสดงอยู่ด้านล่าง:
แต่ละโหนดของโครงสร้างข้อมูลสามารถรับสายจากผู้เยี่ยมชมได้ โหนดนี้จะส่งผ่านวัตถุโหนดไปยังวัตถุผู้เยี่ยมชม และวัตถุผู้เยี่ยมชมจะดำเนินการในการดำเนินการของวัตถุโหนดตามลำดับ กระบวนการนี้เรียกว่า "การจัดส่งสองครั้ง" โหนดจะเรียกผู้เยี่ยมชม ส่งผ่านตัวเองเข้าไป และผู้เยี่ยมชมดำเนินการอัลกอริทึมกับโหนดนี้ แผนผังคลาสไดอะแกรมสำหรับรูปแบบผู้เยี่ยมชมแสดงไว้ด้านล่าง:
บทบาทที่เกี่ยวข้องในโหมดผู้เยี่ยมชมมีดังนี้:
● บทบาทผู้เยี่ยมชมแบบย่อ (ผู้เยี่ยมชม) : ประกาศการดำเนินการของเมธอดอย่างน้อยหนึ่งรายการเพื่อสร้างอินเทอร์เฟซที่บทบาทของผู้เยี่ยมชมเฉพาะทั้งหมดต้องใช้
● บทบาทผู้เยี่ยมชมที่เป็นรูปธรรม (ConcreteVisitor) : ใช้อินเทอร์เฟซที่ประกาศโดยผู้เยี่ยมชมเชิงนามธรรม นั่นคือ การดำเนินการเข้าถึงแต่ละรายการที่ประกาศโดยผู้เยี่ยมชมเชิงนามธรรม
● บทบาทโหนดนามธรรม (โหนด) : ประกาศการดำเนินการยอมรับและยอมรับออบเจ็กต์ผู้เยี่ยมชมเป็นพารามิเตอร์
● บทบาท ConcreteNode : ดำเนินการยอมรับที่ระบุโดยโหนดนามธรรม
● บทบาทโครงสร้างวัตถุ (ObjectStructure) : มีหน้าที่ดังต่อไปนี้ สามารถสำรวจองค์ประกอบทั้งหมดในโครงสร้าง หากจำเป็น ให้จัดเตรียมอินเทอร์เฟซระดับสูงเพื่อให้วัตถุของผู้เยี่ยมชมสามารถเข้าถึงแต่ละองค์ประกอบได้ หากจำเป็น สามารถออกแบบเป็นวัตถุประกอบหรือ คอลเลกชัน เช่น รายการหรือชุด
ซอร์สโค้ด
อย่างที่คุณเห็น บทบาทของผู้เยี่ยมชมเชิงนามธรรมเตรียมการดำเนินการเข้าถึงสำหรับแต่ละโหนดเฉพาะ เนื่องจากมีสองโหนด จึงมีการดำเนินการเข้าถึงที่สอดคล้องกันสองรายการ
คัดลอกรหัสรหัสดังต่อไปนี้:
ผู้เยี่ยมชมอินเทอร์เฟซสาธารณะ {
-
* สอดคล้องกับการดำเนินการเข้าถึงของ NodeA
-
การเยี่ยมชมโมฆะสาธารณะ (โหนด NodeA);
-
* สอดคล้องกับการดำเนินการเข้าถึงของ NodeB
-
การเยี่ยมชมโมฆะสาธารณะ (โหนด NodeB);
-
รหัสการคัดลอกคลาส visitorA เฉพาะมีดังนี้:
VisitorA คลาสสาธารณะดำเนินการ Visitor {
-
* สอดคล้องกับการดำเนินการเข้าถึงของ NodeA
-
@แทนที่
การเยี่ยมชมโมฆะสาธารณะ (โหนด NodeA) {
System.out.println(node.operationA());
-
-
* สอดคล้องกับการดำเนินการเข้าถึงของ NodeB
-
@แทนที่
การเยี่ยมชมโมฆะสาธารณะ (โหนด NodeB) {
System.out.println(node.operationB());
-
-
รหัสคัดลอกคลาส VisitorB ของผู้เยี่ยมชมเฉพาะมีดังนี้:
VisitorB คลาสสาธารณะดำเนินการ Visitor {
-
* สอดคล้องกับการดำเนินการเข้าถึงของ NodeA
-
@แทนที่
การเยี่ยมชมโมฆะสาธารณะ (โหนด NodeA) {
System.out.println(node.operationA());
-
-
* สอดคล้องกับการดำเนินการเข้าถึงของ NodeB
-
@แทนที่
การเยี่ยมชมโมฆะสาธารณะ (โหนด NodeB) {
System.out.println(node.operationB());
-
-
รหัสคัดลอกคลาสโหนดนามธรรมมีดังนี้:
โหนดคลาสนามธรรมสาธารณะ {
-
* ยอมรับการดำเนินการ
-
โมฆะนามธรรมสาธารณะยอมรับ (ผู้เยี่ยมชม);
-
คลาสโหนดเฉพาะ NodeA
คัดลอกรหัสรหัสดังต่อไปนี้:
NodeA คลาสสาธารณะขยายโหนด {
-
* ยอมรับการดำเนินการ
-
@แทนที่
โมฆะสาธารณะยอมรับ (ผู้เยี่ยมชม) {
ผู้เยี่ยมชมเยี่ยมชม (นี้);
-
-
*วิธีการเฉพาะของ NodeA
-
การดำเนินการสตริงสาธารณะA(){
กลับ "NodeA";
-
-
คลาสโหนดเฉพาะ NodeB
คัดลอกรหัสรหัสดังต่อไปนี้:
NodeB คลาสสาธารณะขยายโหนด {
-
*ยอมรับวิธีการ
-
@แทนที่
โมฆะสาธารณะยอมรับ (ผู้เยี่ยมชม) {
ผู้เยี่ยมชมเยี่ยมชม (นี้);
-
-
*วิธีการเฉพาะของ NodeB
-
การดำเนินการสตริงสาธารณะB(){
กลับ "NodeB";
-
-
คลาสบทบาทของอ็อบเจ็กต์โครงสร้าง บทบาทอ็อบเจ็กต์โครงสร้างนี้เก็บคอลเลกชันและจัดเตรียมเมธอด add() ให้กับโลกภายนอกเป็นการดำเนินการจัดการสำหรับคอลเลกชัน โดยการเรียกเมธอดนี้ โหนดใหม่สามารถเพิ่มได้แบบไดนามิก
คัดลอกรหัสรหัสดังต่อไปนี้:
ObjectStructure ระดับสาธารณะ {
รายการส่วนตัว <โหนด> โหนด = ใหม่ ArrayList<โหนด>();
-
* ดำเนินการตามวิธีการ
-
การกระทำที่เป็นโมฆะสาธารณะ (ผู้เยี่ยมชม) {
สำหรับ (โหนดโหนด: โหนด)
-
node.accept (ผู้เยี่ยมชม);
-
-
-
* เพิ่มองค์ประกอบใหม่
-
โมฆะสาธารณะเพิ่ม (โหนดโหนด) {
nodes.add(โหนด);
-
-
รหัสการคัดลอกคลาสไคลเอนต์เป็นดังนี้:
ลูกค้าคลาสสาธารณะ {
โมฆะสาธารณะคงหลัก (สตริง [] args) {
//สร้างวัตถุโครงสร้าง
ObjectStructure os = ObjectStructure ใหม่ ();
//เพิ่มโหนดให้กับโครงสร้าง
os.add(NodeA ใหม่());
//เพิ่มโหนดให้กับโครงสร้าง
os.add(NodeB ใหม่());
//สร้างผู้เยี่ยมชม
ผู้เยี่ยมชม ผู้เยี่ยมชม = ผู้เยี่ยมชมใหม่ ();
os.action(ผู้เยี่ยมชม);
-
-
แม้ว่าโครงสร้างแผนผังอ็อบเจ็กต์ที่ซับซ้อนที่มีโหนดสาขาหลายโหนดจะไม่ปรากฏในการดำเนินการตามแผนผังนี้ แต่ในระบบจริง รูปแบบผู้เยี่ยมชมมักจะใช้เพื่อจัดการโครงสร้างต้นไม้วัตถุที่ซับซ้อน และรูปแบบผู้เยี่ยมชมสามารถใช้เพื่อจัดการกับปัญหาโครงสร้างต้นไม้ที่ขยายหลายลำดับชั้น . นี่คือจุดที่รูปแบบผู้เยี่ยมชมมีประสิทธิภาพมาก
แผนภาพลำดับกระบวนการเตรียมการ
ขั้นแรก ไคลเอนต์ตัวอย่างนี้สร้างออบเจ็กต์โครงสร้าง จากนั้นส่งผ่านออบเจ็กต์ NodeA ใหม่และออบเจ็กต์ NodeB ใหม่
ประการที่สอง ไคลเอนต์สร้างออบเจ็กต์ VisitorA และส่งผ่านออบเจ็กต์นี้ไปยังออบเจ็กต์โครงสร้าง
จากนั้น ไคลเอนต์เรียกวิธีการจัดการการรวมวัตถุโครงสร้างเพื่อเพิ่มโหนด NodeA และ NodeB ให้กับวัตถุโครงสร้าง
ในที่สุดไคลเอนต์เรียกวิธีการดำเนินการ action() ของวัตถุโครงสร้างเพื่อเริ่มกระบวนการเข้าถึง
แผนภาพลำดับกระบวนการเข้าถึง
ออบเจ็กต์โครงสร้างจะสำรวจโหนดทั้งหมดในคอลเลกชันที่บันทึกไว้ ซึ่งในระบบนี้คือโหนด NodeA และ NodeB ขั้นแรก การเข้าถึง NodeA นี้ประกอบด้วยการดำเนินการต่อไปนี้:
(1) เมธอด Accept() ของอ็อบเจ็กต์ NodeA ถูกเรียกและอ็อบเจ็กต์ VisitorA ถูกส่งผ่านเข้าไป
(2) วัตถุ NodeA ในทางกลับกันจะเรียกวิธีการเข้าถึงของวัตถุ VisitorA และส่งผ่านในวัตถุ NodeA เอง
(3) วัตถุ VisitorA เรียกวิธีการเฉพาะ operationA() ของวัตถุ NodeA
ดังนั้นกระบวนการจัดส่งสองครั้งจึงเสร็จสิ้น จากนั้น NodeB จะเข้าถึงได้ กระบวนการเข้าถึงจะเหมือนกับกระบวนการเข้าถึงของ NodeA ซึ่งจะไม่ได้อธิบายไว้ที่นี่
ข้อดีของรูปแบบผู้เยี่ยมชม
● ความสามารถในการขยายที่ดีสามารถเพิ่มฟังก์ชันใหม่ให้กับองค์ประกอบในโครงสร้างวัตถุโดยไม่ต้องแก้ไของค์ประกอบในโครงสร้างวัตถุ
● การนำกลับมาใช้ใหม่ได้ดีทำให้ผู้เยี่ยมชมสามารถกำหนดฟังก์ชันที่ใช้ร่วมกันกับโครงสร้างวัตถุทั้งหมดได้ ซึ่งจะช่วยปรับปรุงระดับของการนำกลับมาใช้ใหม่ได้
● การแยกพฤติกรรมที่ไม่เกี่ยวข้อง คุณสามารถใช้ผู้เข้าชมเพื่อแยกพฤติกรรมที่ไม่เกี่ยวข้อง และสรุปพฤติกรรมที่เกี่ยวข้องเข้าด้วยกันเพื่อสร้างผู้เข้าชม เพื่อให้หน้าที่ของผู้เข้าชมแต่ละคนค่อนข้างเป็นหนึ่งเดียว
ข้อเสียของรูปแบบผู้เข้าชม
● เป็นการยากที่จะเปลี่ยนโครงสร้างออบเจ็กต์ ไม่เหมาะสำหรับสถานการณ์ที่คลาสในโครงสร้างออบเจ็กต์เปลี่ยนแปลงบ่อย เนื่องจากโครงสร้างออบเจ็กต์เปลี่ยนแปลง อินเทอร์เฟซของผู้เยี่ยมชมและการใช้งานของผู้เยี่ยมชมจึงต้องเปลี่ยนแปลงตามไปด้วย ซึ่งมีค่าใช้จ่ายสูงเกินไป
● การทำลายรูปแบบผู้เยี่ยมชมแบบห่อหุ้มมักจะต้องใช้โครงสร้างออบเจ็กต์เพื่อเปิดข้อมูลภายในแก่ผู้เยี่ยมชมและ ObjectStructrue ซึ่งจะแยกการห่อหุ้มของออบเจ็กต์