กลับไปหน้าบทความ

อ่าน 5 นาที

การส่งต่อ (การเขียนโปรแกรมเชิงวัตถุ)

ใน การเขียนโปรแกรมเชิงวัตถุ การส่งต่อ ( forwarding ) หมายถึง การใช้สมาชิกของ วัตถุ หนึ่ง (ไม่ว่าจะเป็น คุณสมบัติ หรือ เมธอด ) จะส่งผลให้มีการใช้สมาชิกที่สอดคล้องกันของวัตถุอื่นแทน...

การส่งต่อ (การเขียนโปรแกรมเชิงวัตถุ)

ในการเขียนโปรแกรมเชิงวัตถุการส่งต่อ ( forwarding ) หมายถึง การใช้สมาชิกของวัตถุ หนึ่ง (ไม่ว่าจะเป็นคุณสมบัติหรือเมธอด ) จะส่งผลให้มีการใช้สมาชิกที่สอดคล้องกันของวัตถุอื่นแทน กล่าวคือ การใช้งานนั้นจะถูกส่งต่อ ไปยังวัตถุอื่น การส่งต่อนี้ใช้ใน รูปแบบการออกแบบหลายแบบโดยที่สมาชิกบางส่วนจะถูกส่งต่อไปยังวัตถุอื่น ในขณะที่สมาชิกอื่นๆ จะถูกจัดการโดยวัตถุที่ใช้งานโดยตรง วัตถุที่ส่งต่อมักเรียกว่าวัตถุห่อหุ้ม (wrapper object ) และสมาชิกที่ส่งต่ออย่างชัดเจนจะเรียกว่าฟังก์ชันห่อหุ้ม (wrapper functions )

คณะผู้แทน

การส่งต่อ (Forwarding) มักถูกเข้าใจผิดว่าเป็นสิ่งเดียวกับการมอบหมาย (Delegation ) ในทางทฤษฎีแล้ว ทั้งสองเป็นแนวคิดที่เสริมกัน ในทั้งสองกรณี มีวัตถุสองชิ้น และวัตถุชิ้นแรก (ผู้ส่ง ผู้ห่อหุ้ม) จะใช้วัตถุชิ้นที่สอง (ผู้รับ ผู้ถูกห่อหุ้ม) ตัวอย่างเช่น เพื่อเรียกเมธอด ความแตกต่างอยู่ที่สิ่งที่selfอ้างถึงในวัตถุผู้รับ (ในทางทฤษฎี คือ ในสภาพแวดล้อมการประเมินของเมธอดบนวัตถุผู้รับ): ในการมอบหมายจะอ้างถึงวัตถุผู้ส่ง ในขณะที่ในการส่งต่อจะอ้างถึงวัตถุผู้รับ โปรดทราบว่าselfมักถูกใช้โดยปริยายเป็นส่วนหนึ่งของการส่งแบบไดนามิก (การระบุเมธอด: ชื่อเมธอดอ้างถึงฟังก์ชันใด)

ความแตกต่างระหว่างการส่งต่อและการมอบหมายคือการผูกพารามิเตอร์ตัวเองใน wrappee เมื่อถูกเรียกผ่าน wrapper ในกรณีของการมอบหมาย พารามิเตอร์ตัวเองจะถูกผูกกับ wrapper ในกรณีของการส่งต่อ พารามิเตอร์ตัวเองจะถูกผูกกับ wrappee ... การส่งต่อเป็นรูปแบบหนึ่งของการส่งข้อความซ้ำโดยอัตโนมัติ การมอบหมายเป็นรูปแบบหนึ่งของการสืบทอดโดยมีการผูกกับ parent (superclass) ในเวลาทำงาน แทนที่จะเป็นเวลาคอมไพล์/ลิงก์เหมือนกับการสืบทอดแบบ 'ปกติ' [ 1 ]

ตัวอย่างเช่น จากโค้ดต่อไปนี้:

// Sender void n () { print ( "n1" ); }// ตัวรับvoid m () { print ( "m2, " ); n (); }void n () { print ( "n2" ); }

ภายใต้การมอบหมายm()จะส่งผลลัพธ์ออกมาม.2, น.1เนื่องจากn()มีการประเมินในบริบทของวัตถุต้นฉบับ (ผู้ส่ง) ในขณะที่ภายใต้การส่งต่อ ผลลัพธ์ที่ได้จะเป็นดังนี้ม.2, น.2เนื่องจากn()มีการประเมินในบริบทของวัตถุที่รับ[ 1 ]

ในการใช้งานทั่วไป การส่งต่อมักถูกเรียกว่า "การมอบหมาย" หรือถือว่าเป็นรูปแบบหนึ่งของการมอบหมาย แต่ในการใช้งานอย่างระมัดระวัง จะมีการแยกแยะความแตกต่างระหว่างสองสิ่งนี้อย่างชัดเจน โดยselfการมอบหมายนั้นคล้ายคลึงกับการสืบทอดทำให้สามารถนำพฤติกรรมกลับมาใช้ใหม่ได้ (และโดยเฉพาะอย่างยิ่งการนำโค้ดกลับมาใช้ใหม่ ) โดยไม่ต้องเปลี่ยนบริบทการประเมิน ในขณะที่การส่งต่อคล้ายคลึงกับการประกอบเพราะการดำเนินการขึ้นอยู่กับวัตถุที่รับ (สมาชิก) เท่านั้น ไม่ใช่วัตถุที่ส่ง (ดั้งเดิม) ในทั้งสองกรณี การนำมาใช้ใหม่เป็นแบบไดนามิก หมายความว่าถูกกำหนดในเวลาทำงาน (ขึ้นอยู่กับวัตถุที่มอบหมายหรือส่งต่อการใช้งาน) มากกว่าแบบคงที่ หมายความว่าถูกกำหนดในเวลาคอมไพล์/ลิงก์ (ขึ้นอยู่กับคลาสที่สืบทอดมา) เช่นเดียวกับการสืบทอด การมอบหมายทำให้วัตถุที่ส่งสามารถแก้ไขพฤติกรรมดั้งเดิมได้ แต่มีความเสี่ยงต่อปัญหาที่คล้ายคลึงกับคลาสพื้นฐานที่เปราะบางในขณะที่การส่งต่อให้การห่อหุ้มที่แข็งแกร่งกว่าและหลีกเลี่ยงปัญหาเหล่านี้ ดูการประกอบเหนือการสืบทอด[ 1 ]

ตัวอย่าง

ตัวอย่างง่ายๆ ของการส่งต่อแบบชัดเจนใน Java: อินสแตนซ์หนึ่งBส่งต่อการเรียกไปยังfooเมธอดของaฟิลด์ของมัน:

คลาสB { A a ; T foo () { return a . foo (); } }

โปรดทราบว่าเมื่อทำการเรียกใช้เมธอด `executing` a.foo()นั้นthisอ็อบเจ็กต์ที่ได้จะเป็นa(ชนิดย่อยของ `Abstract` A) ไม่ใช่อ็อบเจ็กต์ดั้งเดิม (อินสแตนซ์ของ `Abstract` B) นอกจากนี้ ` Abstract` aไม่จำเป็นต้องเป็นอินสแตนซ์ของA`Abstract` ก็ได้ อาจเป็นอินสแตนซ์ของชนิดย่อยก็ได้ และที่จริงแล้ว ` Abstract` Aไม่จำเป็นต้องเป็นคลาสด้วยซ้ำ อาจเป็นอินเทอร์เฟซ/ โปรโตคอลก็ได้

แตกต่างจากการสืบทอดแบบเดิม ซึ่งเมธอดfooถูกกำหนดไว้ในคลาสแม่A(ซึ่งต้องเป็นคลาส ไม่ใช่อินเทอร์เฟซ) และเมื่อเรียกใช้กับอินสแตนซ์ของคลาสลูก เมธอดBนั้นจะใช้โค้ดที่กำหนดไว้ใน คลาสแม่ Aแต่thisตัววัตถุนั้นก็ยังคงเป็นอินสแตนซ์ของคลาสBแม่อยู่ดี

คลาสA { T foo () { /* ... */ }; }คลาสB สืบทอดมาจากA { }

ในตัวอย่าง Python นี้ คลาสBจะส่งต่อfooเมธอดและxคุณสมบัติไปยังอ็อบเจ็กต์ในaฟิลด์ของมัน การใช้สิ่งเหล่านี้กับb(อินสแตนซ์ของB) จะเหมือนกับการใช้สิ่งเหล่านี้กับb.a(อินสแตนซ์ของAที่ส่งต่อสิ่งเหล่านี้ไปให้)

คลาสA : def __init__ ( self , x ) -> None : self . x = xdef foo ( self ): print ( self . x )คลาสB : def __init__ ( self , a ) -> None : self . a = adef foo ( self ): self . a . foo ()@property def x ( self ): return self . a . x@x . setter def x ( self , x ): self . a . x = x@x . deleter def x ( self ): del self . a . xa = A ( 42 ) b = B ( a ) b . foo () # พิมพ์ '42' b . x # มีค่าเป็น '42' b . x = 17 # ตอนนี้ bax มีค่าเป็น 17 del b . x # ลบ bax

เรียบง่าย

แผนภาพคลาส UML ที่แสดงการส่งต่อข้อมูล
แผนภาพคลาส UML ที่แสดงการส่งต่อข้อมูล

ในตัวอย่างJava นี้ Printerคลาสมีprintเมธอด `print` เมธอด `print` นี้แทนที่จะทำการพิมพ์เอง จะส่งต่อไปยังอ็อบเจ็กต์ของคลาส ` RealPrinterprint` แทน จากภายนอกจะดูเหมือนว่าPrinterอ็อบเจ็กต์นั้นกำลังทำการพิมพ์ แต่RealPrinterในความเป็นจริงแล้ว อ็อบเจ็กต์นั้นต่างหากที่เป็นผู้ทำการพิมพ์

การส่งต่อสินค้าหมายถึงการมอบหน้าที่ให้ผู้อื่น/สิ่งอื่นไปทำแทน นี่คือตัวอย่างง่ายๆ:

คลาสRealPrinter { // ตัวรับข้อมูลvoid print () { System . out . println ( "Hello world!" ); } }คลาสPrinter { // " ผู้ส่ง" RealPrinter p = new RealPrinter (); // สร้างตัวรับvoid print () { p.print (); // เรียกใช้ตัวรับ} } public class Main { public static void main ( String [] arguments ) { // ใน สายตาคนภายนอก ดูเหมือนว่า Printer จะพิมพ์ออกมาจริงๆPrinter printer = new Printer (); printer.print ( ) ; } }

ซับซ้อน

กรณีที่ซับซ้อนกว่าคือDecorator Patternซึ่งการใช้อินเทอร์เฟซจะช่วยให้การส่งต่อมีความยืดหยุ่นและปลอดภัยต่อประเภทข้อมูล มากขึ้น "ความยืดหยุ่น" ในที่นี้หมายความว่าCไม่จำเป็นต้องอ้างอิงถึงอินเทอ ร์เฟซ AหรือBในลักษณะใดๆ เนื่องจากกระบวนการสลับการส่งต่อถูกแยกออกมาจาก อินเทอร์เฟซ Cในตัวอย่างนี้ คลาสCสามารถส่งต่อไปยังคลาสใดก็ได้ที่ใช้งาน อินเทอร์เฟซ IคลาสCมีเมธอดสำหรับสลับไปยังตัวส่งต่ออื่น การรวมimplementsเงื่อนไขช่วยเพิ่มความปลอดภัยต่อประเภทข้อมูลเนื่องจากแต่ละคลาสต้องใช้งานเมธอดในอินเทอร์เฟซ ข้อเสียหลักคือโค้ดที่มากขึ้น

interface I { void f (); void g (); } class A implements I { public void f () { System . out . println ( "A: doing f()" ); } public void g () { System . out . println ( "A: doing g()" ); } } class B implements I { public void f () { System . out . println ( "B: doing f()" ); } public void g () { System . out . println ( "B: doing g()" ); } } // การเปลี่ยนอ็อบเจ็กต์ที่ใช้งานในรันไทม์ (ปกติจะทำในคอมไพล์ไทม์) class C implements I { I i = null ; // การส่งต่อpublic C ( I i ){ setI ( i ); } public void f () { i . f (); } public void g () { i . g (); } // คุณสมบัติปกติpublic void setI ( I i ) { this . i = i ; } } public class Main { public static void main ( String [] arguments ) { C c = new C ( new A ()); c . f (); // output: A: doing f() c . g (); // output: A: doing g() c . setI ( new B ()); c . f(); // ผลลัพธ์: B: กำลังทำ f() c . g (); // ผลลัพธ์: B: กำลังทำ g() } }

แอปพลิเคชัน

การส่งต่อถูกใช้ในรูปแบบการออกแบบหลายแบบ[ 2 ]การส่งต่อถูกใช้โดยตรงในรูปแบบต่างๆ ดังนี้:

การส่งต่อข้อมูลอาจถูกนำไปใช้ในรูปแบบอื่นๆ แต่โดยทั่วไปแล้วจะมีการปรับเปลี่ยนการใช้งาน ตัวอย่างเช่น การเรียกเมธอดบนอ็อบเจ็กต์หนึ่งจะส่งผลให้มีการเรียกเมธอดที่แตกต่างกันหลายๆ เมธอดบนอ็อบเจ็กต์อื่น:

ดึงข้อมูลมาจาก " https://en.wikipedia.org/w/index.php?title=Forwarding_(object-oriented_programming)&oldid=1321127166 "

สรุปเนื้อหา

ข้อมูลสำคัญจากบทความ

ข้อมูลสำคัญเกี่ยวกับ การส่งต่อ (การเขียนโปรแกรมเชิงวัตถุ)

ใน การเขียนโปรแกรมเชิงวัตถุ การส่งต่อ ( forwarding ) หมายถึง การใช้สมาชิกของ วัตถุ หนึ่ง (ไม่ว่าจะเป็น คุณสมบัติ หรือ เมธอด ) จะส่งผลให้มีการใช้สมาชิกที่สอดคล้องกันของวัตถุอื่นแทน...

คณะผู้แทน

การส่งต่อ (Forwarding) มักถูกเข้าใจผิดว่าเป็นสิ่งเดียวกับ การมอบหมาย (Delegation ) ในทางทฤษฎีแล้ว ทั้งสองเป็นแนวคิดที่เสริมกัน ในทั้งสองกรณี มีวัตถุสองชิ้น และวัตถุชิ้นแรก (ผู้ส่ง ผู้ห่อหุ้ม) จะใช้วัตถุชิ้นที่สอง (ผู้รับ ผู้ถูกห่อหุ้ม) ตัวอย่างเช่น...

ตัวอย่าง

ตัวอย่างง่ายๆ ของการส่งต่อแบบชัดเจนใน Java: อินสแตนซ์หนึ่ง B ส่งต่อการเรียกไปยัง foo เมธอดของ a ฟิลด์ของมัน:

เรียบง่าย

ในตัวอย่าง Java นี้ Printer คลาส มี print เมธอด `print` เมธอด `print` นี้แทนที่จะทำการพิมพ์เอง จะส่งต่อไปยังอ็อบเจ็กต์ของคลาส ` RealPrinter print` แทน จากภายนอกจะดูเหมือนว่า Printer อ็อบเจ็กต์นั้นกำลังทำการพิมพ์ แต่ RealPrinter ในความเป็นจริงแล้ว...