ละเว้นการสืบทอดในภาษาการเขียนโปรแกรม


10

ฉันกำลังพัฒนาภาษาการเขียนโปรแกรมของตัวเอง เป็นภาษาที่ใช้งานทั่วไป (คิดว่า Python สำหรับพิมพ์บนเดสก์ท็อปคือint x = 1;) ไม่ได้มีไว้สำหรับคลาวด์

คุณคิดว่ามันโอเคที่จะไม่ยอมให้มีการสืบทอดหรือมิกซ์อิน? (เนื่องจากผู้ใช้อย่างน้อยจะมีส่วนต่อประสาน)

ตัวอย่างเช่น: Google Goภาษาของระบบที่สร้างความตกใจให้แก่ชุมชนการเขียนโปรแกรมโดยไม่อนุญาตให้สืบทอด

คำตอบ:


4

ในความเป็นจริงเราพบมากขึ้นเรื่อย ๆ ว่าการใช้การสืบทอดเป็นสิ่งที่ไม่ดีนัก

มรดกการดำเนินงานสามารถเสมอถูกแทนที่โดยการรับมรดกอินเตอร์เฟซบวกองค์ประกอบและการออกแบบซอฟแวร์ที่ทันสมัยมากขึ้นมีแนวโน้มที่จะไปสู่ทิศทางของการใช้มรดกน้อยลงและน้อยในความโปรดปรานขององค์ประกอบ

ดังนั้นใช่ไม่ให้มรดกโดยเฉพาะอย่างยิ่งเป็นที่ถูกต้องสมบูรณ์และมากในสมัยการตัดสินใจการออกแบบ

Mixins บนมืออื่น ๆ ที่ไม่ได้ (ยัง) เป็นคุณลักษณะหลักภาษาและภาษาที่ทำให้คุณลักษณะที่เรียกว่า“mixin” มักจะเข้าใจสิ่งที่แตกต่างกันมากโดยมัน รู้สึกอิสระที่จะให้มันหรือไม่ โดยส่วนตัวฉันคิดว่ามันมีประโยชน์มาก แต่การนำไปใช้อย่างถูกต้องอาจเป็นเรื่องยากมาก


13

การให้เหตุผลว่าการสืบทอด (หรือคุณลักษณะใด ๆ จริง ๆ ) เป็นสิ่งจำเป็นหรือไม่โดยไม่คำนึงถึงความหมายที่เหลือของภาษานั้นไม่มีจุดหมาย คุณกำลังโต้เถียงในสุญญากาศ

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

ตัวอย่างเช่นหากภาษาของคุณมีฟังก์ชั่นชั้นหนึ่งการใช้งานฟังก์ชั่นบางส่วนประเภทข้อมูล polymorphic ตัวแปรประเภทและประเภททั่วไปคุณได้ครอบคลุมฐานเดียวกันมากเหมือนกับที่คุณจะได้รับมรดก OOP คลาสสิก แต่ใช้กระบวนทัศน์ที่แตกต่างกัน

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

(การค้นหาตัวอย่างสำหรับกระบวนทัศน์ทั้งสองที่ระบุไว้นั้นเป็นแบบฝึกหัดสำหรับผู้อ่าน)

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


9

ใช่.

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

หากคุณมีการพิมพ์แบบไดนามิก - และฉันสมมติว่าคุณ - คุณสามารถมีความหลากหลายโดยไม่ต้องสืบทอดเช่นกัน นอกจากนี้หากคุณอนุญาตให้เพิ่มวิธีการโดยพลการให้กับวัตถุที่รันไทม์คุณจะไม่ต้องการสิ่งต่าง ๆ เช่นมิกซ์อินมาก ๆ

ตัวเลือกอื่น - ซึ่งฉันคิดว่าเป็นตัวเลือกที่ดี - คือเลียนแบบ JavaScript และใช้ต้นแบบ เหล่านี้ง่ายกว่าชั้นเรียน แต่ก็มีความยืดหยุ่นมาก สิ่งที่ต้องพิจารณา

ดังนั้นทั้งหมดบอกว่าฉันไปเพื่อมัน


2
ตราบใดที่ยังมีวิธีที่เหมาะสมในการทำงานตามประเภทที่สืบทอดมาโดยทั่วไปให้ดำเนินต่อไป คุณอาจลองใช้เคสสักสองสามกรณีเพื่อให้แน่ใจ (อาจเป็นไปได้ที่จะขอตัวอย่างปัญหาจากคนอื่น ๆ เพื่อหลีกเลี่ยงความลำเอียง ... )
comingstorm

ไม่มันเป็นแบบคงที่และพิมพ์อย่างรุนแรงฉันจะพูดถึงเรื่องนั้นที่ด้านบน
คริสโตเฟอร์

เนื่องจากมันถูกพิมพ์แบบสแตติกคุณไม่สามารถเพิ่มวิธีการแบบสุ่มไปยังวัตถุที่รันไทม์ อย่างไรก็ตามคุณยังสามารถพิมพ์เป็ดได้: ลองดูโปรโตคอล Gosuเพื่อดูตัวอย่าง ฉันใช้โปรโตคอลเล็กน้อยช่วงฤดูร้อนและพวกเขาก็มีประโยชน์จริงๆ Gosu ยังมี "การปรับปรุง" ซึ่งเป็นวิธีการเพิ่มวิธีการเรียนหลังจากข้อเท็จจริง คุณสามารถลองเพิ่มอะไรแบบนั้นได้เช่นกัน
Tikhon Jelvis

2
ได้โปรดได้โปรดได้โปรดหยุดเกี่ยวกับการสืบทอดและการนำโค้ดกลับมาใช้ใหม่ เป็นที่ทราบกันมานานแล้วว่าการสืบทอดเป็นเครื่องมือที่ไม่ดีจริงๆสำหรับการนำโค้ดกลับมาใช้ใหม่
Jan Hudec

3
การสืบทอดเป็นเครื่องมือเดียวสำหรับการใช้รหัสซ้ำ บ่อยครั้งเป็นเครื่องมือที่ดีมากและบางครั้งมันก็น่ากลัว การชอบการเรียงความเรื่องการสืบทอดไม่ได้หมายความว่าไม่เคยใช้การสืบทอดเลย
Michael K

8

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

ในความเป็นจริงมีเหตุผลที่ดีมากในการลบการสืบทอดการติดตั้งเนื่องจากสามารถสร้างรหัสที่ซับซ้อนและยากที่จะรักษารหัส ฉันยังไปไกลถึงเรื่องการถ่ายทอดทางพันธุกรรม (ตามปกติแล้วจะนำไปใช้ในภาษา OOP ส่วนใหญ่) ว่าเป็นความผิดพลาด

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

ต่อไปนี้เป็นวิดีโอที่ฉันพบว่าให้ความกระจ่างมากในหัวข้อทั่วไปนี้ที่ Rich Hickey ระบุแหล่งที่มาของความซับซ้อนในภาษาการเขียนโปรแกรม (รวมถึงการสืบทอด) และนำเสนอทางเลือกสำหรับแต่ละรายการ: ง่าย ๆ ทำได้ง่าย


ในหลายกรณีการสืบทอดส่วนต่อประสานนั้นเหมาะสมกว่า แต่หากใช้กับการกลั่นกรองมรดกการปลูกฝังสามารถอนุญาตให้มีการลบรหัสหม้อไอน้ำจำนวนมากและเป็นทางออกที่หรูหราที่สุด มันแค่ต้องการโปรแกรมเมอร์ที่ฉลาดกว่าและระมัดระวังมากกว่าลิง Python / JavaScript โดยเฉลี่ย
Erik Alapää

4

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

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

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


ใช่สิ่งที่กระตุ้นคำถามนี้คือการนำ SVG DOM มาใช้ซึ่งการใช้รหัสซ้ำมีประโยชน์มาก
คริสโตเฟอร์

2

หากไม่เห็นด้วยกับคำตอบอื่น: ไม่คุณต้องละทิ้งคุณลักษณะเมื่อคุณลักษณะเหล่านี้ไม่สอดคล้องกับสิ่งที่คุณต้องการเพิ่มเติม Java (และภาษา GC'd อื่น ๆ ) สร้างการจัดการหน่วยความจำอย่างชัดเจนเพราะต้องการความปลอดภัยในการพิมพ์มากขึ้น Haskell ขว้างการกลายพันธุ์ออกมาเพราะมันต้องการเหตุผลที่เท่าเทียมกัน แม้แต่ซีก็ยังโยน (หรือประกาศสิ่งผิดกฎหมาย) นามแฝงบางประเภทและพฤติกรรมอื่น ๆ เพราะมันต้องการคอมไพเลอร์เพิ่มประสิทธิภาพมากขึ้น

ดังนั้นคำถามคือคุณต้องการอะไรมากกว่ามรดก?


1
เฉพาะการจัดการหน่วยความจำที่ชัดเจนและความปลอดภัยของประเภทนั้นไม่เกี่ยวข้องอย่างสมบูรณ์และไม่เข้ากันแน่นอน เช่นเดียวกับการกลายพันธุ์และ "ประเภทแฟนซี" ฉันเห็นด้วยกับเหตุผลทั่วไป แต่ตัวอย่างของคุณค่อนข้างดี
Konrad Rudolph

@ KonradRudolph การจัดการหน่วยความจำและความปลอดภัยของประเภทมีความสัมพันธ์กันอย่างใกล้ชิด free/ deleteการดำเนินงานที่จะช่วยให้คุณสามารถที่จะอ้างอิงโมฆะ; เว้นแต่ระบบประเภทของคุณจะสามารถติดตามการอ้างอิงที่ได้รับผลกระทบทั้งหมดซึ่งทำให้ภาษานั้นไม่ปลอดภัย โดยเฉพาะอย่างยิ่งทั้ง C และ C ++ นั้นไม่ได้เป็นประเภทที่ปลอดภัย มันเป็นความจริงที่คุณสามารถประนีประนอมในหนึ่งหรืออื่น ๆ (เช่นประเภทเชิงเส้นหรือข้อ จำกัด การจัดสรร) เพื่อให้พวกเขาเห็นด้วย จะแม่นยำฉันควรจะได้กล่าวว่า Java ต้องการความปลอดภัยประเภทด้วยโดยเฉพาะอย่างยิ่งระบบการพิมพ์ที่ง่ายและไม่ จำกัด การจัดสรรเพิ่มเติม
Ryan Culpepper

ดีขึ้นอยู่กับว่าคุณกำหนดประเภทความปลอดภัยอย่างไร ตัวอย่างเช่นถ้าคุณใช้คำนิยาม (สมเหตุสมผลทั้งหมด) ของความปลอดภัยประเภทเวลารวบรวมและต้องการความสมบูรณ์ของการอ้างอิงเพื่อเป็นส่วนหนึ่งของระบบพิมพ์ของคุณ Java ก็ไม่ได้พิมพ์อย่างปลอดภัย (เนื่องจากอนุญาตให้มีnullการอ้างอิง) การห้ามfreeเป็นข้อ จำกัด เพิ่มเติมโดยพลการ โดยสรุปไม่ว่าภาษานั้นจะปลอดภัยหรือไม่นั้นขึ้นอยู่กับนิยามของความปลอดภัยของประเภทของคุณมากกว่าในภาษานั้น ๆ
Konrad Rudolph

1
@ Ryan: พอยน์เตอร์นั้นปลอดภัยอย่างสมบูรณ์แบบ พวกเขาปฏิบัติตามคำจำกัดความของพวกเขาเสมอ อาจไม่ใช่วิธีที่คุณชอบ แต่เป็นไปตามที่กำหนดไว้เสมอ คุณกำลังพยายามที่จะยืดพวกเขาให้เป็นสิ่งที่พวกเขาไม่ได้ พอยน์เตอร์อัจฉริยะสามารถรับประกันความปลอดภัยของหน่วยความจำได้เล็กน้อยใน C ++
DeadMG

@KonradRudolph: ใน Java ทุกTอ้างอิงหมายถึงอย่างใดอย่างหนึ่งnullหรือวัตถุของคลาสขยายT; nullเป็นที่น่าเกลียด แต่การดำเนินการเกี่ยวกับการnullโยนข้อยกเว้นที่กำหนดไว้อย่างดีแทนที่จะทำลายประเภทไม่แปรเปลี่ยนข้างต้น ตรงกันข้ามกับ C ++: หลังจากการเรียกร้องให้deleteมีT*ตัวชี้อาจชี้ไปที่หน่วยความจำที่ไม่ได้ถือเป็นTวัตถุ ที่แย่กว่านั้นคือทำการกำหนดฟิลด์โดยใช้ตัวชี้นั้นในการกำหนดคุณอาจอัปเดตฟิลด์ของวัตถุของคลาสอื่นโดยสิ้นเชิงหากเกิดขึ้นเพื่อวางไว้ที่ที่อยู่ใกล้เคียง นั่นไม่ใช่ความปลอดภัยประเภทโดยคำจำกัดความที่เป็นประโยชน์ของคำ
Ryan Culpepper

1

เลขที่

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


8
มันแทบไม่เป็นความจริงเลย: การออกแบบทั้งหมดของ Java นั้นเกี่ยวกับการลบคุณลักษณะต่างๆเช่นการใช้งานตัวดำเนินการมากเกินไปการรับมรดกจำนวนมากและการจัดการหน่วยความจำด้วยตนเอง นอกจากนี้ฉันคิดว่าการใช้ภาษาต่าง ๆ ในลักษณะ "ศักดิ์สิทธิ์" นั้นผิด แทนที่จะปรับการลบคุณลักษณะคุณควรปรับการเพิ่ม - ฉันไม่สามารถคิดถึงคุณลักษณะใด ๆ ที่ทุกภาษาต้องมี
Tikhon Jelvis

6
@TikhonJelvis: ซึ่งเป็นเหตุผลว่าทำไม Java เป็นภาษาที่น่ากลัวและมันก็ขาดอะไรเลยยกเว้น "การใช้งาน GARBAGE-COLLECTED INHERITANCE" เป็นเหตุผลอันดับหนึ่ง ฉันเห็นด้วยกับคุณสมบัติทางภาษาที่ควรจะเป็นธรรม แต่สิ่งนี้เป็นคุณสมบัติพื้นฐานของภาษา - มันมีแอปพลิเคชั่นที่มีประโยชน์มากมายและโปรแกรมเมอร์ไม่สามารถจำลองแบบได้โดยไม่ละเมิด DRY
DeadMG

1
@DeadMG ว้าว! ภาษาที่ค่อนข้างแรง
คริสโตเฟอร์

@DeadMG: ฉันเริ่มเขียนข้อโต้แย้งเป็นความคิดเห็น แต่แล้วฉันก็เปลี่ยนเป็นคำตอบ (qv)
Ryan Culpepper

1
"ความสมบูรณ์แบบไม่ได้เกิดขึ้นเมื่อไม่มีอะไรเหลือให้เพิ่มอีก แต่เมื่อไม่มีอะไรเหลือให้เอาออก" (q)
9000

1

การพูดจากมุมมอง C ++ อย่างเคร่งครัดการสืบทอดมีประโยชน์สำหรับวัตถุประสงค์หลักสองประการ:

  1. จะช่วยให้รหัส resusability
  2. เมื่อใช้ร่วมกับการเอาชนะในคลาสลูกจะช่วยให้คุณใช้ตัวชี้คลาสพื้นฐานเพื่อจัดการกับวัตถุคลาสลูกโดยไม่ทราบชนิดของมัน

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

ประโยชน์ของการลบมรดกคือ

  1. ป้องกันลำดับชั้นยาวและปัญหาที่เกี่ยวข้อง กลไกการแบ่งปันรหัสของคุณควรดีพอสำหรับสิ่งนั้น
  2. หลีกเลี่ยงการเปลี่ยนแปลงในคลาสผู้ปกครองจากการแบ่งคลาสย่อย

ข้อเสียเปรียบส่วนใหญ่อยู่ระหว่าง "อย่าทำซ้ำตัวเอง" และมีความยืดหยุ่นในด้านหนึ่งและหลีกเลี่ยงปัญหาในด้านอื่น ๆ โดยส่วนตัวฉันเกลียดที่จะเห็นการสืบทอดจาก C ++ เพียงเพราะนักพัฒนาบางคนอาจไม่ฉลาดพอที่จะมองข้ามปัญหา


1

คุณคิดว่ามันโอเคที่จะไม่ยอมให้มีการสืบทอดหรือมิกซ์อิน? (เนื่องจากผู้ใช้อย่างน้อยจะมีส่วนต่อประสาน)

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

การขาดการสืบทอดการใช้งานมีข้อเสียอย่างหนึ่งที่ชัดเจน: คุณจะไม่สามารถสร้าง vtables ที่จัดทำดัชนีเป็นตัวเลขสำหรับคลาสของคุณและดังนั้นจะต้องทำการค้นหาแฮชสำหรับการเรียกใช้เมธอดทุกครั้ง (หรือหาวิธีที่ชาญฉลาดเพื่อหลีกเลี่ยงพวกมัน) สิ่งนี้อาจเจ็บปวดหากคุณกำหนดเส้นทางแม้แต่ค่าพื้นฐาน (เช่นตัวเลข) ผ่านกลไกนี้ แม้แต่การนำแฮชที่ดีมาใช้ก็อาจมีราคาแพงเมื่อคุณตีมันหลายครั้งในทุกวงใน!

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