ทำไมหลีกเลี่ยงการสืบทอด Java "ขยาย"


40

Jame Gosling กล่าว

“ คุณควรหลีกเลี่ยงการสืบทอดการปฏิบัติเมื่อทำได้”

และใช้การสืบทอดส่วนต่อประสานแทน

แต่ทำไม เราจะหลีกเลี่ยงการสืบทอดโครงสร้างของวัตถุโดยใช้คำสำคัญ "ขยาย" และในเวลาเดียวกันทำให้รหัสของเราเป็นวัตถุเชิง?

ใครช่วยกรุณายกตัวอย่าง Object Oriented ที่แสดงแนวคิดนี้ในสถานการณ์เช่น "สั่งซื้อหนังสือในร้านหนังสือ"



ปัญหาคือคุณสามารถขยายได้เพียงชั้นเดียวเท่านั้น

คำตอบ:


38

ลูกห่านไม่ได้พูดเพื่อหลีกเลี่ยงการใช้คำหลักที่ขยาย ... เขาบอกว่าจะไม่ใช้การสืบทอดเป็นวิธีการเพื่อให้ได้รหัสกลับมาใช้ใหม่

การสืบทอดนั้นไม่เลวต่อ se และเป็นเครื่องมือ (จำเป็น) ที่ทรงพลังมากสำหรับใช้ในการสร้างโครงสร้าง OO อย่างไรก็ตามเมื่อไม่ได้ใช้อย่างถูกต้อง (นั่นคือเมื่อใช้อย่างอื่นนอกเหนือจากการสร้างโครงสร้างวัตถุ) มันสร้างรหัสที่แน่นมากและยากที่จะรักษา

เนื่องจากการสืบทอดควรใช้สำหรับการสร้างโครงสร้าง polymorphic จึงสามารถทำได้อย่างง่ายดายด้วยอินเตอร์เฟสดังนั้นคำสั่งให้ใช้อินเตอร์เฟสแทนที่จะเป็นคลาสเมื่อออกแบบโครงสร้างวัตถุของคุณ หากคุณพบว่าตัวเองกำลังใช้วิธีการขยายเพื่อหลีกเลี่ยงการคัดลอกโค้ดจากวัตถุหนึ่งไปยังอีกวัตถุหนึ่งบางทีคุณอาจใช้ผิดและจะได้รับบริการที่ดีกว่ากับคลาสพิเศษเพื่อจัดการฟังก์ชั่นนี้และแบ่งปันรหัสผ่านองค์ประกอบแทน

อ่านเกี่ยวกับหลักการชดเชย Liskovและทำความเข้าใจ เมื่อใดก็ตามที่คุณกำลังจะขยายชั้นเรียน (หรืออินเทอร์เฟซสำหรับเรื่องนั้น) ถามตัวเองว่าคุณกำลังละเมิดหลักการหรือไม่ถ้าใช่คุณมีแนวโน้มมากที่สุดที่จะใช้การสืบทอดในลักษณะที่ผิดธรรมชาติ มันง่ายที่จะทำและมักจะดูเหมือนสิ่งที่ถูกต้อง แต่มันจะทำให้รหัสของคุณยากต่อการบำรุงรักษาทดสอบและพัฒนา

--- แก้ไข คำตอบนี้ทำให้การโต้แย้งที่ดีในการตอบคำถามในแง่ทั่วไปมากขึ้น


2
(Implementation) การสืบทอดไม่จำเป็นสำหรับการสร้างโครงสร้าง OO คุณสามารถมีภาษา OO ได้หากไม่มีมัน
Andres F.

คุณช่วยยกตัวอย่างได้ไหม ฉันไม่เคยเห็น OO โดยไม่มีการสืบทอด
Newtopian

1
ปัญหาคือไม่มีคำจำกัดความที่ยอมรับได้ว่า OOP คืออะไร (นอกเหนือจากนั้นแม้ Alan Kay ก็เสียใจที่การตีความคำจำกัดความของเขาร่วมกันโดยเน้นไปที่ "วัตถุ" แทนที่จะเป็น "ข่าวสาร" ในปัจจุบัน) นี่คือความพยายามในการตอบคำถามของคุณ ฉันเห็นด้วยเป็นเรื่องผิดปกติที่จะเห็น OOP โดยไม่มีการสืบทอด ดังนั้นข้อโต้แย้งของฉันเป็นเพียงว่ามันไม่จำเป็น ;)
Andres F.

9

ในวันแรกของการเขียนโปรแกรมเชิงวัตถุมรดกการดำเนินการเป็นค้อนทองคำของรหัสกลับมาใช้ หากมีบางส่วนของฟังก์ชั่นในบางคลาสที่คุณต้องการสิ่งที่ต้องทำคือการสืบทอดต่อสาธารณะจากคลาสนั้น (นี่คือวันที่ C ++ แพร่หลายมากขึ้นกว่า Java) และคุณได้รับฟังก์ชั่น "ฟรี"

ยกเว้นมันไม่ได้ฟรีจริงๆ ผลที่ได้คือลำดับชั้นการสืบทอดที่ซับซ้อนมากซึ่งแทบจะเป็นไปไม่ได้ที่จะเข้าใจรวมถึงต้นไม้ที่มีรูปทรงเพชรซึ่งยากต่อการจัดการ นี่เป็นส่วนหนึ่งของเหตุผลที่ผู้ออกแบบ Java เลือกที่จะไม่สนับสนุนการสืบทอดหลายคลาส

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


6

มรดกได้รวบรวมชื่อเสียงที่น่ากลัวค่อนข้างเมื่อเร็ว ๆ นี้ เหตุผลหนึ่งที่จะหลีกเลี่ยงมันเป็นไปตามหลักการออกแบบของ "การรวมตัวกับการสืบทอด" ซึ่งโดยทั่วไปจะกระตุ้นให้คนไม่ใช้การสืบทอดที่ไม่ได้มีความสัมพันธ์ที่แท้จริงเพียงเพื่อบันทึกการเขียนโค้ดบางอย่าง การสืบทอดประเภทนี้อาจทำให้บางส่วนหายากและแก้ไขข้อบกพร่องได้ยาก เหตุผลที่เพื่อนของคุณอาจแนะนำให้ใช้อินเทอร์เฟซแทนเนื่องจากอินเทอร์เฟซให้โซลูชันที่ยืดหยุ่นและบำรุงรักษาได้ดีขึ้นสำหรับการกระจายของ API บ่อยครั้งที่พวกเขาอนุญาตให้มีการเปลี่ยนแปลงกับ api โดยไม่ทำให้รหัสของผู้ใช้แตกหักซึ่งการเปิดเผยคลาสมักจะทำลายรหัสของผู้ใช้ ตัวอย่างที่ดีที่สุดของสิ่งนี้ใน. Net คือความแตกต่างของการใช้งานระหว่าง List และ IList


5

เนื่องจากคุณสามารถขยายได้เพียงหนึ่งคลาสเท่านั้นในขณะที่คุณสามารถใช้หลายอินเตอร์เฟสได้

ฉันเคยได้ยินว่าการสืบทอดกำหนดสิ่งที่คุณเป็น แต่อินเตอร์เฟสกำหนดบทบาทที่คุณสามารถเล่นได้

อินเทอร์เฟซยังอนุญาตให้ใช้ความหลากหลายที่ดีกว่า

นั่นอาจเป็นเหตุผลส่วนหนึ่ง


ทำไม downvote
Mahmoud Hossam

6
คำตอบจะวางเกวียนก่อนม้า Java อนุญาตให้คุณขยายคลาสได้เพียงคลาสเดียวเนื่องจาก Gosling (ผู้ออกแบบ Java) เป็นข้อต่อ มันเหมือนกับว่าเด็ก ๆ ควรสวมหมวกกันน็อกในขณะที่ขี่จักรยานเพราะนั่นเป็นกฎหมาย นั่นเป็นความจริง แต่ทั้งกฎหมายและคำแนะนำนั้นเกิดจากการหลีกเลี่ยงการบาดเจ็บที่ศีรษะ
JohnMcG

@JohnMcG ฉันไม่ได้อธิบายทางเลือกการออกแบบที่ Gosling ทำฉันเพียงแค่ให้คำแนะนำแก่ผู้เขียนโค้ดโดยปกติแล้วผู้เขียนโค้ดมักไม่สนใจการออกแบบภาษา
Mahmoud Hossam

1

ฉันได้รับการสอนด้วยเช่นกันและฉันชอบอินเทอร์เฟซมากที่สุดเท่าที่จะเป็นไปได้ (แน่นอนว่าฉันยังคงใช้การสืบทอดอยู่ซึ่งเหมาะสม)

สิ่งหนึ่งที่ฉันคิดว่ามันคือ decouple รหัสของคุณจากการใช้งานเฉพาะ สมมติว่าฉันมีคลาสที่ชื่อว่า ConsoleWriter และมันถูกส่งผ่านไปยังเมธอดเพื่อเขียนบางสิ่งออกมาและมันเขียนลงในคอนโซล ตอนนี้สมมติว่าฉันต้องการเปลี่ยนไปใช้การพิมพ์เป็นหน้าต่าง GUI ตอนนี้ฉันต้องปรับเปลี่ยนวิธีการหรือเขียนใหม่ที่ใช้ GUIWriter เป็นพารามิเตอร์ ถ้าฉันเริ่มต้นด้วยการกำหนดอินเทอร์เฟซ IWriter และมีวิธีการใน IWriter ฉันสามารถเริ่มต้นด้วย ConsoleWriter (ซึ่งจะใช้อินเทอร์เฟซ IWriter) และจากนั้นเขียนคลาสใหม่ที่เรียกว่า GUIWriter (ซึ่งยังใช้ IWriter อินเตอร์เฟส) จะต้องเปลี่ยนคลาสที่ถูกส่งผ่านไป

อีกสิ่งหนึ่ง (ซึ่งเป็นจริงสำหรับ C #, ไม่แน่ใจเกี่ยวกับ Java) คือคุณสามารถขยายได้ 1 คลาสเท่านั้น แต่ใช้อินเตอร์เฟสจำนวนมาก ให้บอกว่าฉันมีชั้นเรียนที่ชื่อว่าครูอาจารย์และครูสอนวิชาประวัติศาสตร์ ตอนนี้ MathTeacher และ HistoryTeacher ต่อจากอาจารย์ แต่ถ้าเราต้องการชั้นเรียนที่แสดงถึงคนที่เป็นทั้ง MathTeacher และ HistoryTeacher มันอาจดูยุ่งเหยิงเมื่อพยายามสืบทอดจากหลาย ๆ คลาสเมื่อคุณสามารถทำได้ทีละครั้ง (มีวิธี แต่มันไม่เหมาะที่สุด) ด้วยอินเทอร์เฟซคุณสามารถมี 2 อินเตอร์เฟสที่เรียกว่า IMathTeacher และ IHistoryTeacher จากนั้นมีคลาสหนึ่งที่ขยายจากอาจารย์และใช้อินเทอร์เฟซเหล่านั้น 2

ข้อเสียอย่างหนึ่งที่ใช้อินเทอร์เฟซคือบางครั้งฉันเห็นคนทำซ้ำรหัส (เนื่องจากคุณต้องสร้างการใช้งานสำหรับแต่ละคลาสใช้อินเทอร์เฟซ) อย่างไรก็ตามมีวิธีแก้ไขปัญหานี้อย่างสะอาดเช่นการใช้สิ่งต่าง ๆ เช่นตัวแทน (ไม่แน่ใจ Java เทียบเท่ากับอะไร)

คิดว่าเหตุผลที่ดีที่สุดในการใช้ส่วนต่อประสานมากกว่าการสืบทอดคือการแยกรหัสการใช้งานออก แต่อย่าคิดว่าการสืบทอดนั้นชั่วร้ายเพราะมันยังมีประโยชน์มาก


2
"คุณสามารถขยายได้เพียง 1 คลาส แต่ใช้อินเทอร์เฟซมากมาย" นั่นเป็นความจริงสำหรับ Java และ C #
FrustratedWithFormsDesigner

ทางออกหนึ่งที่เป็นไปได้ของปัญหาการทำสำเนารหัสคือการสร้างคลาสนามธรรมที่ใช้อินเทอร์เฟซและขยายนั้น ใช้ด้วยความระมัดระวัง มีเพียงไม่กี่กรณีที่ฉันเห็นว่าเหมาะสม
Michael K

0

ถ้าคุณสืบทอดจากคลาสคุณต้องพึ่งพาไม่เพียง แต่ในคลาสนั้น แต่คุณต้องให้เกียรติสิ่งที่สัญญาที่สร้างขึ้นโดยลูกค้าของคลาสนั้น ลุงบ๊อบเป็นตัวอย่างที่ดีว่าการละเมิดหลักการทดแทน Liskov อย่างง่ายดายนั้นทำได้ง่ายเพียงใดโดยใช้Squareพื้นฐานขยายประเด็นขัดแย้งสี่เหลี่ยมผืนผ้า อินเทอร์เฟซเนื่องจากไม่มีการใช้งานและไม่มีสัญญาซ่อนเร้น


2
One nitpicking: ปัญหาCircle-ellipseจะยังคงเกิดขึ้นแม้ว่าจะใช้อินเทอร์เฟซบริสุทธิ์เท่านั้นหากวัตถุนั้นไม่แน่นอน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.