กรณีการใช้หลายมรดก


15

Java ละเว้นมรดกหลายในบริเวณที่มัน obviates เป้าหมายการออกแบบของการรักษาภาษาที่เรียบง่าย

ฉันสงสัยว่า Java (พร้อมระบบนิเวศน์) นั้น "ง่าย" หรือไม่ Python ไม่ซับซ้อนและมีหลายมรดก ดังนั้นโดยไม่เป็นส่วนตัวเกินไปคำถามของฉันคือ ...

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


8
Java ละเว้นสิ่งดีๆมากมายอย่างสมบูรณ์ด้วยเหตุผลเพียงเล็กน้อย ฉันไม่คาดหวังเหตุผลที่ดีสำหรับ MI
DeadMG

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

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

Java พิมพ์ชื่อในนาม Python ไม่ได้ สิ่งนี้ทำให้การสืบทอดหลายอย่างง่ายขึ้นทั้งในการนำไปใช้และเข้าใจใน Python
จูลส์

คำตอบ:


11

ข้อดี :

  1. บางครั้งอนุญาตให้สร้างแบบจำลองปัญหาที่ชัดเจนกว่าวิธีอื่น ๆ ในการสร้างแบบจำลอง
  2. หาก parrents ที่แตกต่างกันมีวัตถุประสงค์มุมฉากก็สามารถอนุญาตให้มีการประพันธ์บางชนิด

ข้อเสีย:

  1. หากผู้ปกครองที่แตกต่างกันไม่มีวัตถุประสงค์มุมฉากก็ทำให้ประเภทที่เข้าใจยาก
  2. ไม่ใช่เรื่องง่ายที่จะเข้าใจว่ามีการนำไปใช้ในภาษาใด (ภาษาใด ๆ )

ใน C ++ ตัวอย่างที่ดีของการสืบทอดหลายอย่างที่ใช้ในการประกอบคุณสมบัติ orthogonal คือเมื่อคุณใช้CRTPเช่นตั้งค่าระบบคอมโพเนนต์สำหรับเกม

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

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

ดังที่กล่าวไปแล้วปัญหาหลักที่มีหลายมรดกเกิดขึ้นจากการผสมผสานแนวคิดที่เกี่ยวข้อง มันทำให้ภาษาต้องตั้งค่าการใช้งานที่ซับซ้อน (ดูวิธีที่ C ++ อนุญาตให้เล่นกับปัญหาข้าวหลามตัด ... ) และผู้ใช้ไม่แน่ใจว่าเกิดอะไรขึ้นในการใช้งานนั้น ยกตัวอย่างเช่นการอ่านบทความนี้อธิบายวิธีการที่จะนำมาใช้ใน C ++

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


ฉันจะขอบคุณจริง ๆ ถ้าคุณประดับคำตอบของคุณด้วยปัญหาตัวอย่าง - ที่จะทำให้คำเช่น "orthogonal วัตถุประสงค์" ชัดเจน - แต่ขอบคุณ
treecoder

ตกลงให้ฉันลองเพิ่มบางสิ่ง
Klaim

Ogre3D ไม่ใช่ที่ ๆ ฉันอยากมองหาแรงบันดาลใจในการออกแบบ - คุณเคยเห็นการติดเชื้อ Singleton หรือไม่?
DeadMG

ครั้งแรกทายาทเดี่ยวไม่ได้เป็นซิงเกิลจริงๆการก่อสร้างและการทำลายล้างนั้นชัดเจน ถัดไป Ogre เป็นเลเยอร์เหนือระบบฮาร์ดแวร์ (หรือไดรเวอร์กราฟฟิกหากคุณต้องการ) นั่นหมายความว่าควรมีการแสดงที่ไม่ซ้ำกันเพียงรายการเดียวสำหรับอินเทอร์เฟซระบบ (เช่นรูทหรืออื่น ๆ ) พวกเขาสามารถลบซิงเกิลตันออกได้ แต่นั่นไม่ใช่ประเด็นที่นี่ ฉันหลีกเลี่ยงที่จะชี้ประเด็นนี้โดยไม่สมัครใจเพื่อหลีกเลี่ยงการพูดคุยเกี่ยวกับการหมุนรอบดังนั้นโปรดดูตัวอย่างที่ชี้ไป การใช้ซิงเกิลตันของพวกเขาอาจไม่สมบูรณ์แบบ แต่มันมีประโยชน์ในทางปฏิบัติอย่างชัดเจน (แต่สำหรับระบบที่ไม่ใช่ทุกอย่าง)
Klaim

4

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


+1 นี่เป็นเหตุผลที่ดีที่มีหลายสืบทอด มิกซ์อินมีความหมายเพิ่มเติมเพิ่มเติม ("คลาสนี้ไม่ควรใช้เป็นแบบสแตนด์อโลน")
ashes999

2

ผมคิดว่าทางเลือกที่เป็นไปตามหลักเกี่ยวกับปัญหาอันเนื่องมาจากปัญหาเพชร

ยิ่งไปกว่านั้นมันเป็นไปได้ที่จะหลีกเลี่ยงการใช้มรดกโดยการมอบหมายหรือวิธีการอื่น ๆ

ฉันไม่แน่ใจเกี่ยวกับความหมายของคำถามสุดท้ายของคุณ แต่ถ้าเป็น "ในกรณีใดการสืบทอดหลาย ๆ อย่างมีประโยชน์?" ดังนั้นในทุกกรณีที่คุณต้องการให้วัตถุ A ที่มีฟังก์ชั่นของวัตถุ B และ C เป็นพื้น


2

ฉันจะไม่ขุดคุ้ยที่นี่มากนัก แต่คุณสามารถเข้าใจการสืบทอดหลาย ๆ อย่างใน python ผ่านลิงค์ต่อไปนี้http://docs.python.org/release/1.5.1p1/tut/multiple.html :

กฎเดียวที่จำเป็นในการอธิบายความหมายคือกฎการแก้ปัญหาที่ใช้สำหรับการอ้างอิงแอตทริบิวต์คลาส นี่คือความลึกแรกจากซ้ายไปขวา ดังนั้นหากไม่พบแอ็ตทริบิวต์ใน DerivedClassName จะค้นหาใน Base1 จากนั้น (เรียกซ้ำ) ในคลาสฐานของ Base1 และหากไม่พบที่นั่นจะทำการค้นหาใน Base2 และอื่น ๆ

...

เป็นที่ชัดเจนว่าการใช้มรดกหลายอย่างโดยไม่เจาะจงนั้นเป็นฝันร้ายของการบำรุงรักษาเนื่องจากการเชื่อมั่นใน Python ในการประชุมเพื่อหลีกเลี่ยงความขัดแย้งของชื่อโดยไม่ตั้งใจ ปัญหาที่รู้จักกันดีกับการสืบทอดหลาย ๆ คลาสเป็นคลาสที่ได้มาจากคลาสสองคลาสที่เกิดขึ้นกับคลาสพื้นฐานทั่วไป ในขณะที่มันง่ายพอที่จะเข้าใจว่าเกิดอะไรขึ้นในกรณีนี้ (อินสแตนซ์จะมีสำเนา `` อินสแตนซ์ตัวแปร '' หรือแอตทริบิวต์ข้อมูลที่ใช้โดยคลาสพื้นฐานทั่วไป) แต่ก็ไม่ชัดเจนว่าซีแมนทิกส์เหล่านั้น มีประโยชน์

นี่เป็นเพียงย่อหน้าเล็ก ๆ แต่ใหญ่พอที่จะเคลียร์ข้อสงสัยที่ฉันเดาได้


1

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


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

1

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

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

ที่ฉันได้พบการสืบทอดหลายอย่างที่มีประโยชน์อย่างไม่น่าเชื่อแม้จะเป็นนามธรรมที่สุดอินเตอร์เฟสไร้สัญชาติคืออินเทอร์เฟซที่ไม่เสมือน (NVI) ใน C ++

พวกเขาไม่ได้มีคลาสพื้นฐานที่เป็นนามธรรมมากเท่าอินเทอร์เฟซที่มีเพียงเล็กน้อยในการปรับใช้กับพวกเขาเพื่อบังคับใช้แง่มุมสากลของสัญญาของพวกเขาเนื่องจากพวกเขาไม่ได้ จำกัด วงทั่วๆไปของสัญญา .

ตัวอย่างง่าย ๆ (บางคนอาจตรวจสอบว่าตัวจัดการไฟล์ที่ส่งผ่านนั้นเปิดอยู่หรืออะไรทำนองนั้น):

// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
    // Pre: x should always be greater than or equal to zero.
    void f(int x) /*final*/
    {
        // Make sure x is actually greater than or equal to zero
        // to meet the necessary pre-conditions of this function.
        assert(x >= 0);

        // Call the overridden function in the subtype.
        f_impl(x);
    }

protected:
    // Overridden by a boatload of subtypes which implement
    // this non-virtual interface.
    virtual void f_impl(int x) = 0;
};

ในกรณีนี้อาจfถูกเรียกโดยหนึ่งพันแห่งใน codebase ในขณะที่f_implถูกแทนที่ด้วยคลาสย่อยกว่าร้อย

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

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

มันเป็นสิ่งที่ฉันต้องการให้ทุกภาษามีและต้องการแม้แต่ใน C ++ ว่ามันเป็นแนวคิดดั้งเดิมมากกว่าเล็กน้อย (เช่น: ไม่ต้องการให้เรากำหนดฟังก์ชันแยกต่างหากเพื่อแทนที่)

นี้จะเป็นประโยชน์อย่างยิ่งถ้าคุณไม่ได้ทำเช่นนี้assertล่วงหน้าและตระหนักถึงคุณจำเป็นต้องใช้มันในภายหลังเมื่อบางสถานที่สุ่มใน codebase fกำลังเผชิญหน้ากับค่าลบจะถูกส่งผ่านไปยัง


0

ครั้งแรก: สำเนาของคลาสพื้นฐานหลายฉบับ (ปัญหาเกี่ยวกับ C ++) & คลัปคับระหว่างคลาสพื้นฐานและคลาสที่ได้รับ

ที่สอง: การสืบทอดหลายรายการจากอินเตอร์เฟสที่เป็นนามธรรม


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