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

จากวิกิพีเดีย สารานุกรมเสรี
ข้ามไปที่การนำทาง ข้ามไปที่การค้นหา

ใน การเขียนโปรแกรม เชิงวัตถุ การสืบทอดเป็นกลไกของการสร้างฐานของวัตถุหรือคลาสบนวัตถุอื่น ( การสืบทอดตามต้นแบบ ) หรือคลาส ( การสืบทอดตามคลาส ) โดยคงไว้ซึ่งการ ใช้งานที่คล้ายคลึงกัน ยังถูกกำหนดให้เป็นการรับคลาสใหม่ (คลาสย่อย ) จากคลาสที่มีอยู่เช่น super class หรือbase classแล้วสร้างให้เป็นลำดับชั้นของคลาส ในภาษาเชิงวัตถุตามคลาสส่วนใหญ่ วัตถุที่สร้างขึ้นผ่านการสืบทอด "วัตถุลูก" ได้รับคุณสมบัติและพฤติกรรมทั้งหมดของ "วัตถุหลัก" ยกเว้น: ตัว สร้าง , ตัวทำลายโอเปอเรเตอร์โอเวอร์โหลดและฟังก์ชั่นเพื่อนของคลาสพื้นฐาน การสืบทอดช่วยให้โปรแกรมเมอร์สร้างคลาสที่สร้างขึ้นจากคลาสที่มีอยู่[1]เพื่อระบุการใช้งานใหม่ในขณะที่ยังคงทำงานเหมือนเดิม ( ตระหนักถึงอินเทอร์เฟซ ) นำโค้ดมาใช้ซ้ำและขยายซอฟต์แวร์ดั้งเดิมอย่างอิสระผ่านคลาสสาธารณะและ อินเทอ ร์เฟซ ความสัมพันธ์ของอ็อบเจกต์หรือคลาสผ่านการสืบทอดทำให้เกิดกราฟ acyclic ที่กำกับทิศทาง

การสืบทอดถูกคิดค้นขึ้นในปี 1969 สำหรับSimula [2] และปัจจุบันมีการใช้ในภาษาการเขียนโปรแกรมเชิงวัตถุ มากมาย เช่นJava , C++ , PHPและPython

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

การสืบทอดไม่ควรสับสนกับการพิมพ์ย่อย [3] [4]ในบางภาษาการสืบทอดและการพิมพ์ย่อยเห็นด้วย[a]ในขณะที่ภาษาอื่นแตกต่างกัน โดยทั่วไป การพิมพ์ย่อย จะสร้างความสัมพันธ์ แบบ is-aในขณะที่การสืบทอดจะใช้การนำไปใช้ซ้ำอีกครั้งและสร้างความสัมพันธ์แบบวากยสัมพันธ์ ไม่จำเป็นต้องเป็นความสัมพันธ์เชิงความหมาย (การสืบทอดไม่ได้รับประกันการพิมพ์ย่อยตามพฤติกรรม) ในการแยกแยะแนวคิดเหล่านี้ การพิมพ์ย่อยบางครั้งเรียกว่าการสืบทอดอินเทอร์เฟซ (โดยไม่ยอมรับว่าความเชี่ยวชาญพิเศษของตัวแปรประเภทยังทำให้เกิดความสัมพันธ์ในการพิมพ์ย่อย) ในขณะที่การสืบทอดตามที่กำหนดไว้ในที่นี้เรียกว่าการสืบทอดการใช้งานหรือ การสืบทอด รหัส. [5]ถึงกระนั้น การสืบทอดเป็นกลไกที่ใช้กันทั่วไปในการสร้างความสัมพันธ์แบบย่อย [6]

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

ประเภท

มรดกเดี่ยว
มรดกหลายอย่าง

มรดกมีหลายประเภท ขึ้นอยู่กับกระบวนทัศน์และภาษาเฉพาะ [7]

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

"การสืบทอดหลาย รายการ ... เป็นที่ยอมรับกันโดยทั่วไปว่าเป็นเรื่องยากมากที่จะนำไปใช้อย่างมีประสิทธิภาพ ตัวอย่างเช่น ในบทสรุปของ C++ ในหนังสือของเขาเกี่ยวกับObjective Cนั้นแบรด ค็อกซ์อ้างว่าจริง ๆ แล้วการเพิ่มการสืบทอดหลายรายการให้กับ C++ นั้นเป็นไปไม่ได้ ดังนั้น การสืบทอดหลายรายการจึงดูเหมือน ท้าทายมากขึ้น เนื่องจากฉันได้พิจารณาการสืบทอดหลายรายการตั้งแต่ต้นปี 1982 และพบเทคนิคการปรับใช้ที่ง่ายและมีประสิทธิภาพในปี 1984 ฉันจึงไม่สามารถต้านทานความท้าทายนี้ได้ ฉันคิดว่านี่เป็นกรณีเดียวที่แฟชั่นส่งผลต่อลำดับเหตุการณ์ ." [8]

มรดกหลายระดับ
โดยที่คลาสย่อยได้รับการสืบทอดมาจากคลาสย่อยอื่น ไม่ใช่เรื่องแปลกที่คลาสจะมาจากคลาสที่ได้รับอื่น ดังแสดงในรูป "การสืบทอดหลายระดับ"
มรดกหลายระดับ
คลาสAทำหน้าที่เป็นคลาสพื้นฐานสำหรับคลาสที่ได้รับมา Bซึ่งจะทำหน้าที่เป็นคลาสพื้นฐานสำหรับคลาสที่ได้รับ C คลาสBเรียกว่า คลาสฐาน กลางเนื่องจากมีการเชื่อมโยงสำหรับการสืบทอดระหว่างAและC สายโซ่ABCเรียกว่า เส้นทาง การสืบทอด
คลาสที่ได้รับที่มีการสืบทอดหลายระดับถูกประกาศดังนี้:
คลาสA (...); // คลาสพื้นฐานB : สาธารณะA ( ...); // B มาจาก A Class C : public B (...); // C มาจาก B       
       
       
กระบวนการนี้สามารถขยายไปยังระดับต่างๆ ได้
มรดกตามลำดับชั้น
นี่คือที่ที่หนึ่งคลาสทำหน้าที่เป็นซูเปอร์คลาส (คลาสพื้นฐาน) สำหรับคลาสย่อยมากกว่าหนึ่งคลาส ตัวอย่างเช่น คลาสพาเรนต์ A สามารถมีคลาสย่อยสองคลาส B และ C ทั้งคลาสพาเรนต์ของ B และ C คือ A แต่ B และ C เป็นคลาสย่อยแยกกันสองคลาส
มรดกลูกผสม
การสืบทอดแบบไฮบริดคือการที่การสืบทอดประเภทข้างต้นเกิดขึ้นตั้งแต่สองประเภทขึ้นไป ตัวอย่างนี้คือเมื่อคลาส A มีคลาสย่อย B ซึ่งมีสองคลาสย่อย C และ D ซึ่งเป็นส่วนผสมของทั้งการสืบทอดหลายระดับและการสืบทอดตามลำดับชั้น

ซับคลาสและซูเปอร์คลาส

คลาสย่อย คลาสที่ได้รับ คลาสทายาทหรือคลาสย่อยเป็นคลาสอนุพันธ์แบบแยกส่วน ที่รับค่าเอนทิตี ภาษาตั้งแต่หนึ่งคลาสขึ้นไปจากคลาสอื่นอย่างน้อยหนึ่งคลาส (เรียกว่าsuperclass , base classหรือparent class ) ความหมายของการสืบทอดคลาสแตกต่างกันไปในแต่ละภาษา แต่โดยปกติคลาสย่อยจะสืบทอดตัวแปรอินสแตนซ์และฟังก์ชันสมาชิกของซูเปอร์ คลาสโดยอัตโนมัติ

รูปแบบทั่วไปของการกำหนดคลาสที่ได้รับคือ: [9]

คลาส SubClass : การมองเห็นSuperClass  
{
    // สมาชิกคลาสย่อย
};
  • โคลอนบ่งชี้ว่าคลาสย่อยสืบทอดมาจากซูเปอร์คลาส การเปิดเผยข้อมูลเป็นทางเลือก และถ้ามี อาจเป็นแบบส่วนตัวหรือแบบสาธารณะก็ได้ การเปิดเผยเริ่มต้นเป็นแบบส่วนตัว การมองเห็นระบุว่าคุณลักษณะของคลาสฐาน ได้รับมา แบบส่วนตัวหรือ แบบ สาธารณะ

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

คลาสที่ไม่ใช่คลาสย่อย

ในบางภาษา คลาสอาจถูกประกาศเป็นคลาสที่ไม่สามารถจัดคลาสย่อยได้โดยการเพิ่มตัวดัดแปลงคลาส บางตัว ในการประกาศคลาส ตัวอย่างรวมถึงfinalคีย์เวิร์ดในJavaและC++11เป็นต้นไป หรือsealedคีย์เวิร์ดใน C# ตัวดัดแปลงดังกล่าวจะถูกเพิ่มในการประกาศคลาสก่อนการประกาศclassคีย์เวิร์ดและตัวระบุคลาส คลาสที่ไม่ใช่คลาสย่อยดังกล่าวจำกัดความ สามารถใน การนำ กลับมาใช้ใหม่ ได้ โดยเฉพาะอย่างยิ่งเมื่อนักพัฒนามีการเข้าถึงเฉพาะไบนารี ที่คอมไพล์แล้ว เท่านั้น ไม่ใช่ซอร์สโค้ด

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

วิธีการที่ไม่สามารถลบล้างได้

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

วิธีการเสมือน

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

การมองเห็นของสมาชิกที่สืบทอดมา

ตารางต่อไปนี้แสดงให้เห็นว่าตัวแปรและฟังก์ชันใดบ้างที่สืบทอดมาโดยขึ้นอยู่กับการมองเห็นที่ได้รับเมื่อได้รับคลาส [10]

ทัศนวิสัยระดับฐาน ระดับการมองเห็นที่ได้รับ
อนุพันธ์สาธารณะ อนุพันธ์ส่วนตัว อนุพันธ์ที่ได้รับการคุ้มครอง
  • ส่วนตัว →
  • ป้องกัน →
  • สาธารณะ →
  • ไม่สืบทอด
  • มีการป้องกัน
  • สาธารณะ
  • ไม่สืบทอด
  • ส่วนตัว
  • ส่วนตัว
  • ไม่สืบทอด
  • มีการป้องกัน
  • มีการป้องกัน

แอปพลิเคชัน

การสืบทอดใช้เพื่อเชื่อมโยงสองชั้นเรียนขึ้นไปซึ่งกันและกัน

การเอาชนะ

ภาพประกอบของวิธีการเอาชนะ

ภาษาโปรแกรมเชิงวัตถุจำนวนมากอนุญาตให้คลาสหรืออ็อบเจ็กต์แทนที่การใช้งานด้านหนึ่ง—โดยปกติคือพฤติกรรม—ที่สืบทอดมา กระบวนการนี้เรียกว่าการเอาชนะ. การเอาชนะทำให้เกิดความซับซ้อน: พฤติกรรมรุ่นใดที่อินสแตนซ์ของคลาสที่สืบทอดมาใช้—อันที่เป็นส่วนหนึ่งของคลาสของตัวเอง หรืออันจากคลาสพาเรนต์ (เบส) คำตอบจะแตกต่างกันไปตามภาษาโปรแกรม และบางภาษาสามารถระบุได้ว่าพฤติกรรมเฉพาะนั้นจะไม่ถูกแทนที่ และควรประพฤติตามที่กำหนดโดยคลาสพื้นฐาน ตัวอย่างเช่น ใน C# เมธอดหรือคุณสมบัติพื้นฐานสามารถถูกแทนที่ในคลาสย่อยได้ก็ต่อเมื่อถูกทำเครื่องหมายด้วยโมดิฟายเออร์เสมือน นามธรรม หรือโอเวอร์ไรด์ ในขณะที่ในภาษาการเขียนโปรแกรม เช่น Java สามารถเรียกเมธอดที่แตกต่างกันเพื่อแทนที่เมธอดอื่นได้ [11]ทางเลือกอื่นในการแทนที่คือ การ ซ่อนรหัสที่สืบทอดมา

การใช้รหัสซ้ำ

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

ในตัวอย่าง Python ต่อไปนี้ คลาสย่อยSquareSumComputerและCubeSumComputerจะแทนที่ เมธอด transform()ของคลาสพื้นฐานSumComputer คลาสฐานประกอบด้วยการดำเนินการเพื่อคำนวณผลรวมของกำลังสองระหว่างจำนวนเต็มสองจำนวน คลาสย่อยนำฟังก์ชันทั้งหมดของคลาสฐานกลับมาใช้ใหม่ ยกเว้นการดำเนินการที่แปลงตัวเลขเป็นกำลังสอง แทนที่ด้วยการดำเนินการที่แปลงตัวเลขเป็นสี่เหลี่ยมจัตุรัสและลูกบาศก์ตามลำดับ คลาสย่อยจึงคำนวณผลรวมของกำลังสอง/ลูกบาศก์ระหว่างจำนวนเต็มสองจำนวน

ด้านล่างนี้เป็นตัวอย่างของ Python

คลาส SumComputer : 
    def  __init__ ( self ,  a ,  b ) : 
        self = ตัวเอง_ =   
          

    def  transform ( self ,  x ): 
        เพิ่ม NotImplementedError

    def  inputs ( self ): 
        ระยะ การ กลับ ( self . a , self . b ) 

    def  คำนวณ( self ): 
        return  sum ( self . transform ( ค่า)  สำหรับ ค่า ใน self . อินพุต())

คลาส SquareSumComputer ( SumComputer ): 
    def  transform ( self ,  x ): 
        return  x  *  x

คลาส CubeSumComputer ( SumComputer ): 
    def  transform ( self ,  x ): 
        return  x  *  x  *  x

ในพื้นที่ส่วนใหญ่ การสืบทอดคลาสเพื่อจุดประสงค์เดียวของการใช้รหัสซ้ำนั้นไม่ได้รับความนิยม [ จำเป็นต้องอ้างอิง ]ความกังวลหลักคือการดำเนินการสืบทอดไม่ได้ให้การรับรองใด ๆ ของการ ทดแทน polymorphic - ตัวอย่างของการใช้ซ้ำคลาสไม่จำเป็นต้องถูกแทนที่สำหรับตัวอย่างของคลาสที่สืบทอด เทคนิคทางเลือก การมอบหมาย อย่างชัดเจน ต้องใช้ความพยายามในการเขียนโปรแกรมมากขึ้น แต่หลีกเลี่ยงปัญหาความสามารถในการทดแทน [ ต้องการการอ้างอิง ]ในมรดกส่วนตัว C ++ สามารถใช้เป็นรูปแบบของการสืบทอดการใช้งานโดยไม่มีการทดแทน ในขณะที่มรดกสาธารณะแสดงถึงความสัมพันธ์ที่ "เป็น" และการมอบหมายแสดงถึงความสัมพันธ์ "มี-a" มรดกส่วนตัว (และได้รับการคุ้มครอง) ถือได้ว่าเป็นความสัมพันธ์ที่ "ดำเนินการในแง่ของ" (12)

การใช้การสืบทอดบ่อยครั้งอีกอย่างหนึ่งคือการรับประกันว่าคลาสจะรักษาอินเทอร์เฟซทั่วไปบางอย่าง นั่นคือพวกเขาใช้วิธีเดียวกัน คลาสพาเรนต์สามารถเป็นการรวมกันของการดำเนินการที่นำไปใช้และการดำเนินการที่จะนำไปใช้ในคลาสย่อย บ่อยครั้ง ไม่มีการเปลี่ยนแปลงอินเทอร์เฟซระหว่าง supertype และ subtype - เด็กใช้พฤติกรรมที่อธิบายไว้แทนคลาสพาเรนต์ [13]

การสืบทอดเทียบกับการพิมพ์ย่อย

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

คลาส A { 
 สาธารณะ:
  เป็นโมฆะDoSomethingALike () const {}   
};

คลาส B : สาธารณะA {    
 สาธารณะ:
  เป็นโมฆะDoSomethingBLike () const {}   
};

เป็นโมฆะUseAnA ( const A & a ) {    
  . ทำอะไร เหมือนกัน ();
}

เป็นโมฆะSomeFunc () {  
  ; 
  ใช้AnA ( ); // b สามารถใช้แทน A ได้}  

ในภาษาโปรแกรมที่ไม่สนับสนุนการสืบทอดเป็นกลไกการพิมพ์ย่อยความสัมพันธ์ระหว่างคลาสพื้นฐานและคลาสที่ได้รับนั้นเป็นเพียงความสัมพันธ์ระหว่างการใช้งาน (กลไกสำหรับการใช้รหัสซ้ำ) เมื่อเปรียบเทียบกับความสัมพันธ์ระหว่างประเภท การสืบทอด แม้ในภาษาโปรแกรมที่สนับสนุนการสืบทอดเป็นกลไกการพิมพ์ย่อย ไม่จำเป็นต้องนำมาซึ่ง การพิมพ์ย่อย ตามพฤติกรรม เป็นไปได้ทั้งหมดที่จะสืบทอดคลาสที่วัตถุจะทำงานอย่างไม่ถูกต้องเมื่อใช้ในบริบทที่คาดว่าคลาสหลัก ดู หลักการ ทดแทน Liskov [14] (เปรียบเทียบความหมายแฝง/ความหมาย.) ในภาษา OOP บางภาษา แนวคิดของการใช้รหัสซ้ำและการพิมพ์ย่อยตรงกันเพราะวิธีเดียวที่จะประกาศประเภทย่อยคือการกำหนดคลาสใหม่ที่สืบทอดการใช้งานของอีกประเภทหนึ่ง

ข้อจำกัดในการออกแบบ

การใช้การสืบทอดอย่างกว้างขวางในการออกแบบโปรแกรมทำให้เกิดข้อจำกัดบางประการ

ตัวอย่างเช่น พิจารณาคลาสPersonที่มีชื่อของบุคคล วันเกิด ที่อยู่ และหมายเลขโทรศัพท์ เราสามารถกำหนด subclass ของPerson ที่เรียกว่าStudentที่มีเกรดเฉลี่ยของบุคคลและคลาสที่เรียน และ subclass ของPerson ที่เรียกว่าEmployeeที่มีตำแหน่งงาน นายจ้าง และเงินเดือนของบุคคลนั้น

ในการกำหนดลำดับชั้นการสืบทอดนี้ เราได้กำหนดข้อจำกัดบางอย่างแล้ว ซึ่งไม่ใช่ทั้งหมดที่ต้องการ:

ความโสด
การใช้การสืบทอดเดี่ยว คลาสย่อยสามารถสืบทอดจากซูเปอร์คลาสเพียงตัวเดียว ต่อจากตัวอย่างข้างต้นบุคคลสามารถเป็นนักเรียนหรือลูกจ้างแต่ไม่ใช่ทั้งสองอย่าง การใช้การสืบทอดหลายส่วนช่วยแก้ปัญหานี้ได้เพียงบางส่วน เนื่องจากเราสามารถกำหนด คลาส StudentEmployeeที่สืบทอดมาจากทั้งStudentและEmployee อย่างไรก็ตาม ในการใช้งานส่วนใหญ่ มันยังสามารถสืบทอดจากแต่ละ superclass ได้เพียงครั้งเดียว ดังนั้นจึงไม่สนับสนุนกรณีที่นักเรียนมีสองงานหรือเข้าเรียนสองสถาบัน โมเดลการสืบทอดที่มีในไอเฟลทำให้สิ่งนี้เป็นไปได้โดยการสนับสนุนการสืบทอดซ้ำ
คงที่
ลำดับชั้นการสืบทอดของออบเจ็กต์ได้รับการแก้ไข เมื่อ สร้างอินสแตนซ์เมื่อเลือกประเภทของออบเจ็กต์และไม่เปลี่ยนแปลงตามเวลา ตัวอย่างเช่น กราฟการสืบทอดไม่อนุญาตให้ วัตถุ Studentกลายเป็น วัตถุ Employee โดย ที่ยังคงสถานะSuperclass ของ บุคคล ไว้ (อย่างไรก็ตาม พฤติกรรมแบบนี้สามารถทำได้ด้วยลวดลายมัณฑนากร ) บางคนวิพากษ์วิจารณ์การสืบทอดโดยโต้แย้งว่าผู้พัฒนาล็อคมาตรฐานการออกแบบดั้งเดิมของพวกเขา [15]
ทัศนวิสัย
เมื่อใดก็ตามที่รหัสไคลเอ็นต์สามารถเข้าถึงอ็อบเจ็กต์ได้ โดยทั่วไปจะมีสิทธิ์เข้าถึงข้อมูลซูเปอร์คลาสของอ็อบเจ็กต์ทั้งหมด แม้ว่าซูเปอร์คลาสจะไม่ได้เปิดเผยต่อสาธารณะ แต่ไคลเอ็นต์ยังคงส่งอ็อบเจ็กต์เป็นประเภทซูเปอร์คลาสได้ ตัวอย่างเช่น ไม่มีทางให้ฟังก์ชันเป็นตัวชี้ไปยัง เกรดเฉลี่ยของ นักเรียนและการถอดเสียงโดยไม่ให้ฟังก์ชันนั้นเข้าถึงข้อมูลส่วนบุคคลทั้งหมดที่จัดเก็บไว้ในซูเปอร์คลาสPerson ของนักเรียน ภาษาสมัยใหม่จำนวนมาก รวมถึง C++ และ Java ให้ตัวแก้ไขการเข้าถึง "ที่ได้รับการป้องกัน" ซึ่งช่วยให้คลาสย่อยเข้าถึงข้อมูลได้ โดยไม่อนุญาตให้โค้ดใดๆ ภายนอกห่วงโซ่การสืบทอดเข้าถึงได้

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

ปัญหาและทางเลือกอื่น

การสืบทอดการนำไปใช้เป็นที่ถกเถียงกันในหมู่โปรแกรมเมอร์และนักทฤษฎีของการเขียนโปรแกรมเชิงวัตถุตั้งแต่ช่วงปี 1990 เป็นอย่างน้อย ในหมู่พวกเขามีผู้เขียนDesign Patternsซึ่งสนับสนุนการสืบทอดอินเทอร์เฟซแทน และสนับสนุนการจัดองค์ประกอบมากกว่าการสืบทอด ตัวอย่างเช่น รูปแบบของมัณฑนากร (ดังที่กล่าวไว้ข้างต้น ) ได้รับการเสนอเพื่อเอาชนะลักษณะคงที่ของการสืบทอดระหว่างชั้นเรียน ในฐานะที่เป็นวิธีแก้ปัญหาพื้นฐานที่มากขึ้นสำหรับปัญหาเดียวกันการเขียนโปรแกรมเชิงบทบาทจะแนะนำความสัมพันธ์ที่แตกต่างเล่นโดยการรวมคุณสมบัติของการสืบทอดและองค์ประกอบเข้าในแนวคิดใหม่ [ ต้องการการอ้างอิง ]

จากคำกล่าวของAllen Holubปัญหาหลักของการสืบทอดการนำไปใช้คือมันแนะนำการมีเพศสัมพันธ์ ที่ไม่จำเป็น ในรูปแบบของ"ปัญหาคลาสฐานที่เปราะบาง" : [5]การปรับเปลี่ยนการใช้งานคลาสฐานอาจทำให้เกิดการเปลี่ยนแปลงพฤติกรรมโดยไม่ได้ตั้งใจในคลาสย่อย การใช้อินเทอร์เฟซช่วยหลีกเลี่ยงปัญหานี้เนื่องจากไม่มีการแชร์การใช้งาน เฉพาะ API เท่านั้น [15]อีกวิธีหนึ่งในการระบุสิ่งนี้คือ "มรดกแบ่งการห่อหุ้ม " [16]ปัญหาปรากฏชัดเจนในระบบเชิงวัตถุแบบเปิด เช่นกรอบงานโดยที่รหัสไคลเอ็นต์คาดว่าจะสืบทอดจากคลาสที่ระบบจัดหา จากนั้นจึงแทนที่คลาสของระบบในอัลกอริธึม [5]

ตามรายงานของ James Goslingนักประดิษฐ์ Java ได้ต่อต้านการสืบทอดการนำไปใช้โดยระบุว่าเขาจะไม่รวมมันไว้หากต้องการออกแบบ Java ใหม่ [15]การออกแบบภาษาที่แยกการสืบทอดจากการพิมพ์ย่อย (การสืบทอดส่วนต่อประสาน) ปรากฏเร็วเท่าปี 1990; [17]ตัวอย่างที่ทันสมัยของสิ่งนี้คือภาษาการเขียนโปรแกรม Go

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

ปัญหาอีกประการหนึ่งเกี่ยวกับการสืบทอดคือต้องกำหนดคลาสย่อยในโค้ด ซึ่งหมายความว่าผู้ใช้โปรแกรมไม่สามารถเพิ่มคลาสย่อยใหม่ในขณะรันไทม์ได้ รูปแบบการออกแบบอื่นๆ (เช่นEntity–component–system ) อนุญาตให้ผู้ใช้โปรแกรมกำหนดรูปแบบต่างๆ ของเอนทิตีในขณะรันไทม์

ดูเพิ่มเติม

หมายเหตุ

  1. ^ โดยทั่วไปแล้วจะเป็นจริงเฉพาะในภาษา OO แบบคลาสที่พิมพ์แบบสแตติกเท่านั้น เช่น C++ , C# , Javaและ Scala

อ้างอิง

  1. ^ จอห์นสัน ราล์ฟ (26 สิงหาคม 2534) "การออกแบบชั้นเรียนที่ใช้ซ้ำได้" (PDF) . www.cse.msu.edu _
  2. ^ มิ้นซ์ ไมค์; เอเคนดาห์ล, โรเบิร์ต (2006). การตรวจสอบฮาร์ดแวร์ด้วย C++: คู่มือผู้ปฏิบัติงาน สปริงเกอร์. หน้า 22. ISBN 978-0-387-25543-9.
  3. อรรถเป็น คุก วิลเลียม อาร์.; ฮิลล์ วอลเตอร์; แคนนิ่ง, ปีเตอร์ เอส. (1990). การสืบทอดไม่ใช่การพิมพ์ย่อย การดำเนินการของการประชุมวิชาการ ACM SIGPLAN-SIGACT ครั้งที่ 17 เกี่ยวกับหลักการของภาษาการเขียนโปรแกรม (POPL) น. 125–135. CiteSeerX 10.1.1.102.8635 . ดอย : 10.1145/96709.96721 . ISBN  0-89791-343-4.
  4. ^ คาร์เดลลี ลูก้า (1993). Typeful Programming (รายงานทางเทคนิค) ดิจิตัล อิควิปเมนท์ คอร์ปอเรชั่น . หน้า 32–33. รายงานการวิจัย SRC 45.
  5. อรรถเป็น c Mikhajlov, Leonid; เซเครินสกี้, เอมิล (1998). การศึกษาปัญหาระดับฐานที่เปราะบาง (PDF ) การดำเนินการของการประชุมยุโรปครั้งที่ 12 เกี่ยวกับการเขียนโปรแกรมเชิงวัตถุ (ECOOP) หมายเหตุบรรยายในวิทยาการคอมพิวเตอร์. ฉบับที่ 1445. สปริงเกอร์. น. 355–382. ดอย : 10.1007/BFb0054099 . ISBN  978-3-540-64737-9.
  6. ^ เทมเปโร อีวาน; หยาง, ฮงยูล; โนเบิล, เจมส์ (2013). โปรแกรมเมอร์ทำอะไรกับการสืบทอดใน Java (PDF ) ECOOP 2013 การเขียนโปรแกรมเชิงวัตถุ หมายเหตุบรรยายในวิทยาการคอมพิวเตอร์. ฉบับที่ 7920. สปริงเกอร์. หน้า 577–601. ดอย : 10.1007/978-3-642-39038-8_24 . ISBN  978-3-642-39038-8.
  7. ^ "มรดก C++" . www.cs.nmsu.edu _
  8. สตรูสทรัป, บียาร์น (1994). การออกแบบและวิวัฒนาการของ C ++ เพียร์สัน หน้า 417. ISBN 9780135229477.
  9. ชิลด์ต์, เฮอร์เบิร์ต (2003). การอ้างอิงที่ สมบูรณ์C++ ทาทา แมคกรอว์ฮิลล์. หน้า 417 . ISBN 978-0-07-053246-5.
  10. ^ บาลากูรูซามี อี. (2010). การเขียนโปรแกรมเชิงวัตถุด้วย C ++ ทาทา แมคกรอว์ฮิลล์. หน้า 213. ISBN 978-0-07-066907-9.
  11. ^ แทนที่ (อ้างอิง C#)
  12. ^ "GotW #60: Exception-Safe Class Design, Part 2: Inheritance" . Gotw.ca . สืบค้นเมื่อ2012-08-15 .
  13. ^ Venugopal, KR; Buyya, Rajkumar (2013). เชี่ยวชาญ C ++ Tata McGrawhill Education Private Limited. หน้า 609. ISBN 9781259029943.
  14. มิตเชลล์, จอห์น (2002). "10 "แนวคิดในภาษาเชิงวัตถุ" " แนวคิดในภาษาโปรแกรม . สำนักพิมพ์มหาวิทยาลัยเคมบริดจ์ หน้า  287 . ISBN 978-0-521-78098-8.
  15. อรรถเป็น c Holub อัลเลน (1 สิงหาคม 2546) "ทำไมการขยายจึงชั่วร้าย" . สืบค้นเมื่อ10 มีนาคม 2558 .
  16. เซเทอร์ ลินดา เอ็ม.; พัลส์เบิร์ก, เจนส์; ลีเบอร์แฮร์, คาร์ล เจ. (1996). "วิวัฒนาการของพฤติกรรมวัตถุโดยใช้ความสัมพันธ์เชิงบริบท" . หมายเหตุวิศวกรรม ซอฟต์แวร์ACM SIGSOFT 21 (6): 46. CiteSeerX 10.1.1.36.5053 . ดอย : 10.1145/250707.239108 . 
  17. อเมริกา, ปิแอร์ (1991). การออกแบบภาษาโปรแกรมเชิงวัตถุด้วยการพิมพ์ย่อย ตามพฤติกรรม โรงเรียน REX / การประชุมเชิงปฏิบัติการเรื่องพื้นฐานของภาษาเชิงวัตถุ หมายเหตุบรรยายในวิทยาการคอมพิวเตอร์. ฉบับที่ 489. น. 60–90. ดอย : 10.1007/BFb0019440 . ISBN 978-3-540-53931-5.

อ่านเพิ่มเติม