ทำไมถึงชอบแต่งเพลงมากกว่ามรดก? มีการแลกเปลี่ยนอะไรบ้างในแต่ละวิธี คุณควรเลือกการสืบทอดทางองค์ประกอบมากกว่าเมื่อใด
ทำไมถึงชอบแต่งเพลงมากกว่ามรดก? มีการแลกเปลี่ยนอะไรบ้างในแต่ละวิธี คุณควรเลือกการสืบทอดทางองค์ประกอบมากกว่าเมื่อใด
คำตอบ:
ชอบการแต่งมากกว่าการสืบทอดเนื่องจากสามารถแก้ไขได้ง่ายกว่า / ดัดแปลงได้ในภายหลัง แต่อย่าใช้วิธีการเขียนแบบเสมอ ด้วยการจัดวางองค์ประกอบทำให้ง่ายต่อการเปลี่ยนพฤติกรรมได้ทันทีด้วย Dependency Injection / Setters การสืบทอดมีความเข้มงวดมากขึ้นเนื่องจากภาษาส่วนใหญ่ไม่อนุญาตให้คุณได้รับมากกว่าหนึ่งประเภท ดังนั้นห่านจะสุกมากขึ้นหรือน้อยลงเมื่อคุณได้รับจาก TypeA
การทดสอบกรดของฉันสำหรับด้านบนคือ:
TypeB ต้องการเปิดเผยอินเตอร์เฟสที่สมบูรณ์ (วิธีสาธารณะทั้งหมดไม่น้อยกว่า) ของ TypeA เช่นนั้น TypeB สามารถใช้งานได้ตามที่คาดว่า TypeA หรือไม่ บ่งบอกถึงการถ่ายทอดทางพันธุกรรม
TypeB ต้องการบางส่วน / ส่วนหนึ่งของพฤติกรรมที่เปิดเผยโดย TypeA หรือไม่ บ่งบอกถึงความต้องการองค์ประกอบ
อัปเดต:เพิ่งกลับมาที่คำตอบของฉันและดูเหมือนว่าตอนนี้มันจะไม่สมบูรณ์โดยไม่มีการกล่าวถึงหลักการการทดแทน Liskovของบาร์บาร่าลิฟคอฟเป็นการทดสอบสำหรับ 'ฉันควรได้รับมรดกจากประเภทนี้หรือไม่'
นึกถึงการกักกันว่ามีความสัมพันธ์ รถยนต์ "มี" เครื่องยนต์บุคคล "มีชื่อ" ฯลฯ
คิดว่าในฐานะที่เป็นมรดกเป็นความสัมพันธ์ รถยนต์ "คือ" ยานพาหนะบุคคล "คือ" สัตว์เลี้ยงลูกด้วยนม ฯลฯ
ฉันไม่ได้รับเครดิตสำหรับวิธีการนี้ ฉันเอามันมาจากCode สมบูรณ์ฉบับที่สองโดยSteve McConnell , มาตรา 6.3
ถ้าคุณเข้าใจความแตกต่างมันจะอธิบายได้ง่ายกว่า
ตัวอย่างนี้คือ PHP โดยไม่ต้องใช้คลาส (โดยเฉพาะก่อน PHP5) ตรรกะทั้งหมดถูกเข้ารหัสในชุดฟังก์ชั่น คุณอาจรวมไฟล์อื่น ๆ ที่มีฟังก์ชั่นตัวช่วยและอื่น ๆ และดำเนินการตรรกะทางธุรกิจของคุณโดยการส่งผ่านข้อมูลไปยังฟังก์ชั่น นี่อาจเป็นเรื่องยากมากที่จะจัดการเมื่อแอปพลิเคชันเติบโตขึ้น PHP5 พยายามแก้ไขสิ่งนี้โดยเสนอการออกแบบเชิงวัตถุมากขึ้น
สิ่งนี้ส่งเสริมการใช้คลาส การสืบทอดเป็นหนึ่งในสามหลักของการออกแบบ OO (การสืบทอด, ความหลากหลาย, การห่อหุ้ม)
class Person {
String Title;
String Name;
Int Age
}
class Employee : Person {
Int Salary;
String Title;
}
นี่คือมรดกในที่ทำงาน ลูกจ้าง "คือ" บุคคลหรือสืบทอดจากบุคคล ความสัมพันธ์ของการสืบทอดทั้งหมดคือความสัมพันธ์แบบ "is-a" พนักงานยังแสดงคุณสมบัติของชื่อเรื่องจากบุคคลซึ่งหมายถึง Employee.Title จะส่งคืนชื่อสำหรับพนักงานไม่ใช่บุคคล
องค์ประกอบเป็นที่โปรดปรานมากกว่ามรดก เพื่อให้ง่ายมากคุณจะต้อง:
class Person {
String Title;
String Name;
Int Age;
public Person(String title, String name, String age) {
this.Title = title;
this.Name = name;
this.Age = age;
}
}
class Employee {
Int Salary;
private Person person;
public Employee(Person p, Int salary) {
this.person = p;
this.Salary = salary;
}
}
Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);
องค์ประกอบโดยทั่วไปคือ "มี" หรือ "ใช้ความสัมพันธ์" ที่นี่ชั้นพนักงานมีบุคคล มันไม่ได้รับมรดกจากบุคคล แต่กลับได้รับวัตถุบุคคลส่งผ่านไปซึ่งเป็นเหตุผลว่าทำไม "มี" บุคคล
ตอนนี้พูดว่าคุณต้องการสร้างประเภทผู้จัดการดังนั้นคุณจะได้:
class Manager : Person, Employee {
...
}
ตัวอย่างนี้จะใช้งานได้ดี แต่จะเกิดอะไรขึ้นถ้าทั้งบุคคลและพนักงานประกาศTitle
? Manager.Title ควรส่งคืน "Manager of Operations" หรือ "Mr. " หรือไม่ ภายใต้องค์ประกอบความคลุมเครือนี้จะถูกจัดการที่ดีกว่า:
Class Manager {
public string Title;
public Manager(Person p, Employee e)
{
this.Title = e.Title;
}
}
วัตถุผู้จัดการประกอบด้วยพนักงานและคน พฤติกรรมชื่อเรื่องนั้นมาจากพนักงาน องค์ประกอบที่ชัดเจนนี้จะลบความคลุมเครือในหมู่สิ่งอื่น ๆ และคุณจะพบข้อบกพร่องน้อยลง
ด้วยผลประโยชน์ที่ไม่อาจปฏิเสธได้จากการสืบทอดนี่คือข้อเสียบางประการ
ข้อเสียของการสืบทอด:
ในอีกทางหนึ่งองค์ประกอบของวัตถุถูกกำหนดที่รันไทม์ผ่านวัตถุที่ได้รับการอ้างอิงไปยังวัตถุอื่น ในกรณีเช่นนี้วัตถุเหล่านี้จะไม่สามารถเข้าถึงข้อมูลที่มีการป้องกันซึ่งกันและกัน (ไม่มีการแบ่งการห่อหุ้ม) และจะถูกบังคับให้เคารพอินเทอร์เฟซของกันและกัน และในกรณีนี้การพึ่งพาการใช้งานจะน้อยกว่าในกรณีที่สืบทอด
อีกเหตุผลที่เป็นประโยชน์อย่างมากที่จะชอบการแต่งเพลงมากกว่าการสืบทอดเกี่ยวข้องกับโมเดลโดเมนของคุณและทำการแมปไปยังฐานข้อมูลเชิงสัมพันธ์ เป็นการยากที่จะแมปการสืบทอดกับโมเดล SQL (คุณต้องจบด้วยวิธีการแฮ็กทุกประเภทเช่นการสร้างคอลัมน์ที่ไม่ได้ใช้งานเสมอโดยใช้มุมมอง ฯลฯ ) ORML บางอันพยายามที่จะจัดการกับสิ่งนี้ แต่มันจะซับซ้อนขึ้นอย่างรวดเร็วเสมอ องค์ประกอบสามารถสร้างแบบจำลองได้อย่างง่ายดายผ่านความสัมพันธ์ต่างประเทศที่สำคัญระหว่างสองตาราง แต่การสืบทอดนั้นยากกว่ามาก
ในขณะที่พูดสั้น ๆ ฉันจะเห็นด้วยกับ "ชอบแต่งเพลงมากกว่ามรดก" บ่อยครั้งสำหรับฉันดูเหมือน "ชอบมันฝรั่งมากกว่าโคคา - โคล่า" มีสถานที่สำหรับการสืบทอดและสถานที่สำหรับการจัดองค์ประกอบ คุณต้องเข้าใจความแตกต่างดังนั้นคำถามนี้จะหายไป สิ่งที่มีความหมายสำหรับฉันคือ "ถ้าคุณจะใช้การสืบทอด - คิดอีกครั้งคุณจะต้องมีองค์ประกอบ"
คุณควรเลือกมันฝรั่งมากกว่าโคคาโคล่าเมื่อคุณต้องการที่จะกินและโคคาโคล่ามากกว่ามันฝรั่งเมื่อคุณต้องการที่จะดื่ม
การสร้างคลาสย่อยควรมีความหมายมากกว่าเพียงแค่วิธีที่สะดวกในการเรียกเมธอดซูเปอร์คลาส คุณควรใช้การสืบทอดเมื่อ subclass "is-a" super class มีทั้งโครงสร้างและหน้าที่เมื่อมันสามารถใช้เป็น superclass และคุณจะใช้มัน ถ้าไม่ใช่ในกรณี - มันไม่ใช่มรดก แต่อย่างอื่น องค์ประกอบคือเมื่อวัตถุของคุณประกอบด้วยวัตถุอื่นหรือมีความสัมพันธ์บางอย่างกับพวกเขา
ดังนั้นสำหรับฉันดูเหมือนว่ามีคนไม่ทราบว่าเขาต้องการมรดกหรือองค์ประกอบปัญหาที่แท้จริงคือว่าเขาไม่ทราบว่าเขาต้องการดื่มหรือกิน คิดถึงโดเมนปัญหาของคุณให้มากขึ้นเข้าใจดีขึ้น
InternalCombustionEngine
GasolineEngine
หลังเพิ่มสิ่งต่าง ๆ เช่นหัวเทียนซึ่งคลาสพื้นฐานขาด แต่การใช้สิ่งInternalCombustionEngine
นั้นจะทำให้หัวเทียนถูกใช้งาน
การถ่ายทอดทางพันธุกรรมค่อนข้างน่าดึงดูดโดยเฉพาะอย่างยิ่งที่มาจากขั้นตอนที่ดินและมักจะดูสง่างาม ฉันหมายถึงสิ่งที่ฉันต้องทำคือเพิ่มฟังก์ชันหนึ่งบิตนี้ให้กับคลาสอื่นใช่มั้ย ปัญหาข้อหนึ่งก็คือ
คลาสพื้นฐานของคุณแบ่งการห่อหุ้มโดยเปิดเผยรายละเอียดการใช้งานไปยังคลาสย่อยในรูปแบบของสมาชิกที่ได้รับความคุ้มครอง สิ่งนี้ทำให้ระบบของคุณแข็งแกร่งและบอบบาง ข้อบกพร่องที่น่าเศร้ามากขึ้นคือ subclass ใหม่มาพร้อมกับสัมภาระและความคิดเห็นของห่วงโซ่การสืบทอด
บทความInheritance is Evil: The Epic Fail ของ DataAnnotationsModelBinderเดินผ่านตัวอย่างของสิ่งนี้ใน C # มันแสดงให้เห็นถึงการใช้มรดกเมื่อองค์ประกอบควรจะถูกนำมาใช้และวิธีที่จะสามารถ refactored
ใน Java หรือ C # วัตถุไม่สามารถเปลี่ยนชนิดของมันได้เมื่อวัตถุนั้นถูกสร้างขึ้นมาแล้ว
ดังนั้นหากวัตถุของคุณต้องปรากฏเป็นวัตถุที่แตกต่างหรือมีพฤติกรรมแตกต่างกันไปขึ้นอยู่กับสถานะหรือเงื่อนไขของวัตถุให้ใช้องค์ประกอบ : อ้างถึงรัฐและกลยุทธ์รูปแบบการออกแบบของ
หากวัตถุต้องเป็นประเภทเดียวกันให้ใช้การสืบทอดหรือการนำอินเตอร์เฟสมาใช้
Client
ยกตัวอย่างเช่นในระบบการตลาดที่คุณอาจมีแนวความคิดของการเป็น จากนั้นแนวคิดใหม่ของการPreferredClient
ปรากฏขึ้นในภายหลัง ควรPreferredClient
ได้รับมรดกClient
? ไคลเอนต์ที่ต้องการ 'คือ' ไคลเอนต์ afterall ไม่? ไม่เร็วนัก ... อย่างที่คุณพูดว่าวัตถุไม่สามารถเปลี่ยนคลาสได้ในขณะทำงาน คุณจะทำแบบจำลองการclient.makePreferred()
ทำงานอย่างไร บางทีคำตอบอยู่ในการใช้ประกอบกับแนวคิดที่หายไปเป็นAccount
บางที?
Client
การเรียนอาจจะมีเพียงหนึ่งที่ห่อหุ้มแนวคิดของนั้นAccount
ซึ่งอาจจะเป็นStandardAccount
หรือPreferredAccount
...
ไม่พบคำตอบที่น่าพอใจที่นี่ดังนั้นฉันจึงเขียนคำตอบใหม่
เพื่อให้เข้าใจว่าทำไม " ชอบองค์ประกอบมากกว่าการสืบทอด" ก่อนอื่นเราต้องกลับสมมติฐานที่ละเว้นในสำนวนที่สั้นกว่านี้
มีประโยชน์สองประการของการสืบทอด: การพิมพ์ย่อยและการจัดคลาสย่อย
การพิมพ์ย่อยหมายถึงการปฏิบัติตามลายเซ็นประเภท (อินเตอร์เฟส) เช่นชุดของ API และสามารถแทนที่ส่วนหนึ่งของลายเซ็นเพื่อให้ได้ polymorphism ชนิดย่อย
การจัดคลาสย่อยหมายถึงการนำการใช้งานมาใช้ซ้ำโดยนัย
ด้วยประโยชน์ทั้งสองประการนั้นมีวัตถุประสงค์ที่แตกต่างกันสองประการสำหรับการรับมรดก: การพิมพ์ย่อยและการใช้รหัสซ้ำ
หากการใช้รหัสซ้ำเป็นเพียงวัตถุประสงค์เดียวการแบ่งคลาสอาจให้มากกว่าสิ่งที่เขาต้องการเช่นวิธีการสาธารณะบางอย่างของชั้นผู้ปกครองไม่เหมาะสมสำหรับชั้นเรียนเด็ก ในกรณีนี้แทนนิยมองค์ประกอบมรดกองค์ประกอบจะเรียกร้อง นี่คือที่ซึ่งความคิด "is-a" กับ "has-a" มาจาก
ดังนั้นเมื่อมีการพิมพ์ subtyping เช่นการใช้คลาสใหม่ในภายหลังในลักษณะ polymorphic เราต้องเผชิญกับปัญหาในการเลือกการสืบทอดหรือการจัดองค์ประกอบ นี่คือสมมติฐานที่ถูกตัดออกไปในสำนวนสั้น ๆ ภายใต้การสนทนา
เมื่อต้องการชนิดย่อยคือให้สอดคล้องกับลายเซ็นชนิดนี้หมายความว่าการจัดเรียงจะต้องเปิดเผย API ของประเภทไม่น้อย ตอนนี้การค้าไม่ชอบเตะใน:
Inheritance จัดให้มีการนำรหัสมาใช้อย่างตรงไปตรงมาหากไม่ได้ถูกเขียนทับในขณะที่การเรียบเรียงจะต้องกำหนดรหัสใหม่ทุก API แม้ว่าจะเป็นเพียงการมอบหมายงานง่ายๆ
มรดกให้ตรงไปตรงมาrecursion เปิดผ่านทางเว็บไซต์ภายใน polymorphic this
คืออัญเชิญเอาชนะวิธี (หรือแม้กระทั่งประเภท ) ในการทำงานของสมาชิกคนอื่น ๆ ทั้งภาครัฐและเอกชน (แม้ว่าท้อแท้ ) การเรียกซ้ำแบบเปิดสามารถจำลองได้ผ่านการจัดวางองค์ประกอบแต่ต้องใช้ความพยายามเป็นพิเศษและอาจไม่สามารถทำงานได้ (?) นี้คำตอบของคำถามที่ซ้ำกันพูดถึงสิ่งที่คล้ายกัน
มรดกเปิดเผยสมาชิกที่ได้รับความคุ้มครอง สิ่งนี้จะแบ่งการห่อหุ้มของคลาสพาเรนต์และหากใช้โดยคลาสย่อยจะมีการแนะนำการพึ่งพาระหว่างเด็กกับพาเรนต์อีกครั้ง
องค์ประกอบมี BeFit ผกผันของการควบคุมและการพึ่งพาสามารถฉีดแบบไดนามิกเป็นที่แสดงในรูปแบบมัณฑนากรและรูปแบบการพร็อกซี่
องค์ประกอบที่มีประโยชน์ของCombinator ที่มุ่งเน้นการเขียนโปรแกรมคือการทำงานในลักษณะเหมือนเป็นรูปแบบคอมโพสิต
ด้วยความไม่ชอบในการซื้อขายข้างต้นเราจึงชอบการแต่งเพลงมากกว่ามรดก แต่สำหรับคลาสที่สัมพันธ์กันอย่างแน่นหนาเช่นเมื่อนำโค้ดโดยนัยกลับมาใช้ใหม่ให้เกิดประโยชน์จริงๆหรือต้องการพลังวิเศษของการเรียกซ้ำแบบเปิดการสืบทอดจะเป็นตัวเลือก
โดยส่วนตัวฉันเรียนรู้ที่จะชอบองค์ประกอบมากกว่ามรดก ไม่มีปัญหาการเขียนโปรแกรมที่คุณสามารถแก้ไขได้ด้วยการสืบทอดซึ่งคุณไม่สามารถแก้ไขได้ด้วยการแต่งเพลง แม้ว่าคุณอาจจะต้องใช้อินเทอร์เฟซ (Java) หรือโปรโตคอล (Obj-C) ในบางกรณี เนื่องจาก C ++ ไม่รู้อะไรเลยคุณจะต้องใช้คลาสฐานนามธรรมซึ่งหมายความว่าคุณไม่สามารถกำจัดการสืบทอดใน C ++ ได้อย่างสิ้นเชิง
บ่อยครั้งที่การจัดองค์ประกอบมีเหตุผลมากกว่านี้ทำให้มีนามธรรมที่ดีกว่าการห่อหุ้มที่ดีกว่าการนำโค้ดกลับมาใช้ใหม่ได้ดีขึ้น (โดยเฉพาะในโครงการขนาดใหญ่มาก) และมีโอกาสน้อยกว่าที่จะทำลายทุกอย่างในระยะไกล นอกจากนี้ยังทำให้ง่ายขึ้นในการรักษา " หลักการความรับผิดชอบเดี่ยว " ซึ่งมักจะสรุปว่า " ไม่ควรมีเหตุผลมากกว่าหนึ่งข้อสำหรับชั้นเรียนที่จะเปลี่ยนแปลง " และหมายความว่าทุกชั้นมีอยู่เพื่อจุดประสงค์เฉพาะและควร มีเพียงวิธีการที่เกี่ยวข้องโดยตรงกับจุดประสงค์ของมัน การมีต้นไม้มรดกที่ตื้นมากทำให้ง่ายต่อการเก็บภาพรวมแม้ว่าโครงการของคุณจะเริ่มมีขนาดใหญ่มาก หลายคนคิดว่ามรดกเป็นตัวแทนโลกแห่งความจริงของเราค่อนข้างดี แต่นั่นไม่ใช่ความจริง โลกแห่งความจริงใช้องค์ประกอบมากกว่ามรดก วัตถุทุกอย่างในโลกแห่งความเป็นจริงที่คุณถืออยู่ในมือของคุณนั้นประกอบขึ้นจากวัตถุอื่น ๆ ที่มีขนาดเล็กกว่าจริง
มีข้อเสียขององค์ประกอบแม้ว่า หากคุณข้ามการรับมรดกไปพร้อม ๆ กันและให้ความสำคัญกับการแต่งเพลงคุณจะสังเกตเห็นว่าคุณต้องเขียนโค้ดพิเศษสองสามบรรทัดซึ่งไม่จำเป็นถ้าคุณใช้การสืบทอด บางครั้งคุณถูกบังคับให้ทำซ้ำตัวเองและสิ่งนี้ละเมิดหลักการของ DRY(DRY = อย่าทำซ้ำตัวเอง) องค์ประกอบมักจะต้องมีการมอบหมายและวิธีการที่เป็นเพียงการเรียกวิธีการอื่นของวัตถุอื่นที่ไม่มีรหัสอื่น ๆ โดยรอบการโทรนี้ "การเรียกใช้วิธีการสองครั้ง" ดังกล่าว (ซึ่งอาจขยายไปสู่การเรียกใช้วิธีแบบสามหรือสี่เท่าได้ง่ายกว่าและไกลกว่านั้น) มีประสิทธิภาพที่แย่กว่าการรับมรดกมากซึ่งคุณเพียงแค่สืบทอดวิธีการของผู้ปกครอง การเรียกใช้เมธอดที่สืบทอดอาจเร็วพอ ๆ กับการโทรแบบไม่สืบทอดหรืออาจจะช้ากว่าเล็กน้อย แต่โดยทั่วไปแล้วจะยังเร็วกว่าการโทรด้วยวิธีที่ต่อเนื่องสองครั้ง
คุณอาจสังเกตว่าภาษา OO ส่วนใหญ่ไม่อนุญาตให้สืบทอดหลาย ๆ ในขณะที่มีสองสามกรณีที่มรดกหลายอย่างสามารถซื้ออะไรให้คุณได้ แต่สิ่งเหล่านั้นเป็นข้อยกเว้นมากกว่ากฎ เมื่อใดก็ตามที่คุณพบสถานการณ์ที่คุณคิดว่า "การสืบทอดหลายครั้งจะเป็นคุณสมบัติที่ยอดเยี่ยมจริงๆในการแก้ปัญหานี้" คุณมักจะมาถึงจุดที่คุณควรคิดการรับมรดกใหม่อีกครั้งเนื่องจากอาจต้องใช้คู่โค้ดเพิ่มเติม การแก้ปัญหาตามองค์ประกอบมักจะกลายเป็นหลักฐานที่สง่างามยืดหยุ่นและอนาคต
การสืบทอดเป็นคุณสมบัติที่ยอดเยี่ยม แต่ฉันเกรงว่ามันจะถูกใช้มากเกินไปในช่วงสองสามปีที่ผ่านมา ผู้คนถือว่าการสืบทอดเป็นค้อนเดียวที่สามารถตอกตะปูได้ทั้งหมดไม่ว่าจะเป็นตะปูสกรูหรืออาจเป็นสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง
TextFile
File
กฎง่ายๆทั่วไปของฉัน: ก่อนที่จะใช้การสืบทอดให้พิจารณาว่าการเรียงความมีเหตุผลมากกว่านี้หรือไม่
เหตุผล: คลาสย่อยมักจะหมายถึงความซับซ้อนและความเชื่อมโยงมากขึ้นเช่นยากที่จะเปลี่ยนแปลงดูแลรักษาและปรับขนาดโดยไม่ทำผิดพลาด
คำตอบที่สมบูรณ์และเป็นรูปธรรมมากขึ้นจาก Tim Boudreau of Sun:
ปัญหาที่พบบ่อยในการใช้การสืบทอดตามที่ฉันเห็นคือ:
- การกระทำที่ไร้เดียงสาสามารถให้ผลลัพธ์ที่ไม่คาดคิดได้ตัวอย่างคลาสสิกของสิ่งนี้คือการเรียกใช้วิธี overridable จาก superclass constructor ก่อนที่ฟิลด์อินสแตนซ์คลาสย่อยจะถูกเตรียมใช้งาน ในโลกที่สมบูรณ์แบบไม่มีใครเคยทำเช่นนั้น นี่ไม่ใช่โลกที่สมบูรณ์แบบ
- มันเสนอการล่อลวงที่ผิดปกติสำหรับ subclassers เพื่อตั้งสมมติฐานเกี่ยวกับลำดับของการเรียกเมธอดและเช่น - สมมติฐานดังกล่าวมีแนวโน้มที่จะไม่เสถียรถ้าซุปเปอร์คลาสอาจพัฒนาไปตามกาลเวลา ดูเพิ่มเติมปิ้งขนมปังและหม้อกาแฟของฉันเปรียบเทียบ
- ชั้นเรียนหนักขึ้น - คุณไม่จำเป็นต้องรู้ว่าซุปเปอร์คลาสของคุณกำลังทำอะไรอยู่ในคอนสตรัคเตอร์หรือหน่วยความจำที่ใช้งานอยู่ ดังนั้นการสร้างวัตถุน้ำหนักเบาที่ไร้เดียงสาจะมีราคาแพงกว่าที่คุณคิดและสิ่งนี้อาจเปลี่ยนแปลงได้เมื่อเวลาผ่านไปหากซุปเปอร์คลาสเปลี่ยนไป
- มันส่งเสริมให้เกิดการระเบิดของ subclasses เวลาในการโหลดคลาส, หน่วยความจำเพิ่มขึ้น นี่อาจไม่ใช่ปัญหาจนกว่าคุณจะจัดการกับแอปในระดับ NetBeans แต่ที่นั่นเรามีปัญหาจริงเช่นเมนูช้าเนื่องจากการแสดงครั้งแรกของเมนูทำให้เกิดการโหลดคลาสขนาดใหญ่ เราแก้ไขสิ่งนี้โดยการย้ายไปยังไวยากรณ์ที่เปิดเผยมากขึ้นและเทคนิคอื่น ๆ แต่ใช้เวลาในการแก้ไขเช่นกัน
- มันทำให้ยากที่จะเปลี่ยนแปลงสิ่งต่าง ๆ ในภายหลัง - ถ้าคุณได้เปิดเผยให้ชั้นเรียนการแลกเปลี่ยน superclass จะทำลายคลาสย่อย - เป็นทางเลือกที่เมื่อคุณทำให้รหัสสาธารณะคุณแต่งงานแล้ว ดังนั้นหากคุณไม่ได้เปลี่ยนฟังก์ชั่นการใช้งานจริงเป็นซูเปอร์คลาสของคุณคุณจะมีอิสระมากขึ้นในการเปลี่ยนสิ่งต่าง ๆ ในภายหลังหากคุณใช้แทนที่จะขยายสิ่งที่คุณต้องการ ยกตัวอย่างเช่นการจัดคลาส JPanel - นี่เป็นความผิดปกติ และถ้าคลาสย่อยนั้นเป็นที่สาธารณะคุณจะไม่มีโอกาสได้กลับมาตัดสินใจอีก หากเข้าถึงได้ในรูปแบบของ JComponent getThePanel () คุณยังสามารถทำได้ (คำใบ้: เปิดเผยโมเดลสำหรับส่วนประกอบภายในเป็น API ของคุณ)
- ลำดับชั้นของวัตถุไม่ได้ปรับขนาด (หรือทำให้ขยายได้ในภายหลังยากกว่าการวางแผนล่วงหน้า) - นี่คือปัญหา "เลเยอร์มากเกินไป" แบบคลาสสิก ฉันจะไปที่ด้านล่างนี้และวิธีการที่รูปแบบ AskTheOracle สามารถแก้ไขได้อย่างไร
...
สิ่งที่ฉันต้องทำถ้าคุณยอมให้รับมรดกซึ่งคุณสามารถเอาไปกับเม็ดเกลือได้:
- เปิดเผยไม่มีฟิลด์ยกเว้นค่าคงที่
- วิธีการจะเป็นนามธรรมหรือสุดท้าย
- ไม่มีวิธีการโทรจากตัวสร้าง superclass
...
ทั้งหมดนี้ใช้กับโครงการขนาดเล็กน้อยกว่าโครงการขนาดใหญ่และชั้นเรียนส่วนตัวน้อยกว่าโครงการสาธารณะ
ดูคำตอบอื่น ๆ
มันมักจะพูดว่าชั้นBar
สามารถสืบทอดชั้นFoo
เมื่อประโยคต่อไปนี้เป็นจริง:
- แถบคือ foo
น่าเสียดายที่การทดสอบข้างต้นเพียงอย่างเดียวไม่น่าเชื่อถือ ใช้สิ่งต่อไปนี้แทน:
- แถบคือ foo และ
- บาร์สามารถทำทุกอย่างที่ foos ทำได้
เพื่อให้แน่ใจว่าการทดสอบครั้งแรกทั้งหมดgettersของFoo
ให้ความรู้สึกในการBar
(= คุณสมบัติที่ใช้ร่วมกัน) ในขณะที่การทดสอบที่สองทำให้แน่ใจว่าทุกsettersของFoo
ให้ความรู้สึกในการBar
(= การทำงานร่วมกัน)
ตัวอย่างที่ 1: สุนัข -> สัตว์
สุนัขเป็นสัตว์และสุนัขสามารถทำทุกอย่างที่สัตว์สามารถทำได้ (เช่นการหายใจการตาย ฯลฯ ) ดังนั้นคลาสDog
สามารถสืบทอดคลาสAnimal
ได้
ตัวอย่างที่ 2: วงกลม - / -> วงรี
วงกลมเป็นวง แต่ไม่สามารถทำทุกอย่างที่วงรีทำได้ ตัวอย่างเช่นวงกลมไม่สามารถยืดได้ในขณะที่จุดไข่ปลาสามารถทำได้ ดังนั้นคลาสCircle
ไม่สามารถสืบทอดคลาสEllipse
ได้
สิ่งนี้เรียกว่าปัญหาวงเวียนซึ่งจริงๆแล้วไม่ใช่ปัญหาเพียงหลักฐานที่ชัดเจนว่าการทดสอบครั้งแรกเพียงอย่างเดียวไม่เพียงพอที่จะสรุปได้ว่าการถ่ายทอดนั้นเป็นไปได้ โดยเฉพาะอย่างยิ่งตัวอย่างนี้ไฮไลท์ที่ได้มาเรียนควรขยายการทำงานของฐานเรียนที่ไม่เคยจำกัดมัน มิฉะนั้นคลาสฐานไม่สามารถใช้ polymorphically
แม้ว่าคุณจะสามารถใช้การสืบทอดไม่ได้หมายความว่าคุณควร : การใช้การแต่งเพลงเป็นตัวเลือกเสมอ การสืบทอดเป็นเครื่องมือที่มีประสิทธิภาพที่อนุญาตให้ใช้รหัสโดยนัยและการจัดส่งแบบไดนามิก แต่มีข้อเสียเล็กน้อยซึ่งเป็นสาเหตุที่มักจะต้องการการแต่งเพลง การค้าเพลย์ออฟระหว่างมรดกและองค์ประกอบไม่ได้ที่เห็นได้ชัดและในความคิดของฉันมีการอธิบายที่ดีที่สุดในคำตอบของ LCN
ตามกฎของหัวแม่มือฉันมักจะเลือกสืบทอดมากกว่าองค์ประกอบเมื่อใช้ polymorphic คาดว่าจะพบบ่อยมากในกรณีที่พลังของการจัดส่งแบบไดนามิกสามารถนำไปสู่ API ที่อ่านได้และสง่างามมากขึ้น ตัวอย่างเช่นการมีคลาส polymorphic Widget
ในกรอบงาน GUI หรือคลาส polymorphic Node
ในไลบรารี XML อนุญาตให้มี API ซึ่งอ่านได้ง่ายกว่าและใช้งานได้ง่ายกว่าสิ่งที่คุณมีกับการแก้ปัญหาล้วนๆตามองค์ประกอบ
วิธีการอื่นที่ใช้ในการพิจารณาว่าการสืบทอดนั้นเป็นไปได้หรือไม่นั้นเรียกว่าหลักการทดแทน Liskov :
ฟังก์ชันที่ใช้พอยน์เตอร์หรือการอ้างอิงไปยังคลาสพื้นฐานต้องสามารถใช้วัตถุของคลาสที่ได้รับโดยไม่ต้องรู้
โดยพื้นฐานแล้วนี่หมายความว่าการสืบทอดเป็นไปได้ถ้าคลาสฐานสามารถใช้ polymorphically ได้ซึ่งฉันเชื่อว่าเทียบเท่ากับการทดสอบของเรา "บาร์เป็นฟูและบาร์สามารถทำทุกอย่างที่ foos สามารถทำได้"
computeArea(Circle* c) { return pi * square(c->radius()); }
จินตนาการฟังก์ชั่น เห็นได้ชัดว่ามันหักถ้าผ่านวงรี (รัศมี) หมายถึงอะไร? วงรีไม่ใช่วงกลมและเป็นเช่นนั้นไม่ควรได้รับจาก Circle
computeArea(Circle *c) { return pi * width * height / 4.0; }
ตอนนี้มันเป็นเรื่องธรรมดา
width()
และheight()
? เกิดอะไรขึ้นถ้าตอนนี้ผู้ใช้ห้องสมุดตัดสินใจที่จะสร้างคลาสอื่นที่เรียกว่า "EggShape" มันควรจะมาจาก "Circle" หรือไม่? ไม่แน่นอน รูปร่างไข่ไม่ใช่วงกลมและวงรีไม่ใช่วงกลมเช่นกันดังนั้นจึงไม่มีใครควรได้รับจาก Circle เนื่องจากมันแบ่ง LSP วิธีการดำเนินการกับคลาส Circle * ทำให้สมมติฐานที่แข็งแกร่งเกี่ยวกับสิ่งที่เป็นวงกลมและการทำลายสมมติฐานเหล่านี้จะนำไปสู่ข้อบกพร่องอย่างแน่นอน
การสืบทอดมีประสิทธิภาพมาก แต่คุณไม่สามารถบังคับได้ (ดู: ปัญหาวงกลมวงรี ) หากคุณไม่แน่ใจอย่างแท้จริงเกี่ยวกับความสัมพันธ์แบบย่อย "is-a" จริงคุณควรใช้การแต่งเพลง
การสืบทอดสร้างความสัมพันธ์ที่แข็งแกร่งระหว่างคลาสย่อยและซุปเปอร์คลาส subclass ต้องระวังรายละเอียดการใช้งานของคลาส super การสร้างซุปเปอร์คลาสนั้นยากขึ้นมากเมื่อคุณต้องคิดว่ามันจะขยายได้อย่างไร คุณต้องจัดทำเอกสารค่าคงที่ของคลาสอย่างระมัดระวังและระบุวิธีการอื่น ๆ ที่วิธี overridable ใช้ภายใน
บางครั้งการสืบทอดมีประโยชน์ถ้าลำดับชั้นแสดงถึงความสัมพันธ์แบบ มันเกี่ยวข้องกับหลักการเปิดปิดซึ่งระบุว่าคลาสควรจะปิดสำหรับการปรับเปลี่ยน แต่เปิดเพื่อขยาย ด้วยวิธีนี้คุณสามารถมีความหลากหลาย ที่จะมีวิธีการทั่วไปที่เกี่ยวข้องกับประเภท super และวิธีการของมัน แต่ผ่านการจัดส่งแบบไดนามิกวิธีการ subclass ถูกเรียก สิ่งนี้มีความยืดหยุ่นและช่วยในการสร้างทางอ้อมซึ่งเป็นสิ่งจำเป็นในซอฟต์แวร์ (เพื่อทราบรายละเอียดการใช้งานน้อยลง)
การสืบทอดนั้นใช้งานง่ายเกินไปและสร้างความซับซ้อนเพิ่มเติมโดยมีการขึ้นต่อกันระหว่างคลาสอย่างหนัก นอกจากนี้ยังเข้าใจว่าเกิดอะไรขึ้นระหว่างการทำงานของโปรแกรมที่ได้รับยากเนื่องจากเลเยอร์และการเลือกวิธีการโทรแบบไดนามิก
ฉันขอแนะนำให้ใช้การเขียนเป็นค่าเริ่มต้น มันเป็นแบบแยกส่วนมากขึ้นและให้ประโยชน์ของการผูกปลาย (คุณสามารถเปลี่ยนองค์ประกอบแบบไดนามิก) นอกจากนี้ยังง่ายต่อการทดสอบสิ่งต่าง ๆ แยกกัน และถ้าคุณต้องการใช้วิธีจากคลาสคุณจะไม่ถูกบังคับให้อยู่ในรูปแบบที่แน่นอน (หลักการทดแทน Liskov)
Inheritance is sometimes useful... That way you can have polymorphism
ว่าเป็นการเชื่อมโยงอย่างหนักเกี่ยวกับแนวคิดของการสืบทอดและความหลากหลาย (การพิมพ์ย่อยให้ตามบริบท) ความคิดเห็นของฉันมีจุดมุ่งหมายเพื่อชี้ให้เห็นสิ่งที่คุณชี้แจงในความคิดเห็นของคุณ: การสืบทอดไม่ใช่วิธีเดียวที่จะใช้ความหลากหลายและในความเป็นจริงไม่จำเป็นต้องเป็นปัจจัยกำหนดเมื่อตัดสินใจเลือกระหว่างองค์ประกอบและการสืบทอด
สมมติว่าเครื่องบินมีเพียงสองส่วน: เครื่องยนต์และปีก
จากนั้นมีสองวิธีในการออกแบบคลาสเครื่องบิน
Class Aircraft extends Engine{
var wings;
}
ตอนนี้เครื่องบินของคุณสามารถเริ่มต้นด้วยการมีปีกคงที่
และเปลี่ยนเป็นปีกหมุนได้ทันที มัน
เป็นเครื่องยนต์ที่มีปีก แต่ถ้าฉันต้องการเปลี่ยน
เครื่องยนต์ในทันทีด้วยล่ะ
คลาสฐานอาจEngine
ทำให้ mutator เปลี่ยน
คุณสมบัติหรือฉันออกแบบใหม่Aircraft
เป็น:
Class Aircraft {
var wings;
var engine;
}
ตอนนี้ฉันสามารถเปลี่ยนเครื่องยนต์ได้ทันทีเช่นกัน
คุณต้องดูที่หลักการการทดแทน LiskovในหลักการSOLIDของการออกแบบชั้นเรียนของลุงบ็อบ :)
เมื่อคุณต้องการ "คัดลอก" / เปิดเผยคลาสพื้นฐาน 'API คุณจะใช้การสืบทอด เมื่อคุณต้องการเพียงแค่ "คัดลอก" ฟังก์ชั่นใช้การมอบหมาย
ตัวอย่างหนึ่งของสิ่งนี้: คุณต้องการสร้างสแต็กออกจากรายการ สแต็คมีเพียงป๊อปดันและแอบดู คุณไม่ควรใช้การสืบทอดเนื่องจากคุณไม่ต้องการ push_back, push_front, removeAt, และฟังก์ชันการทำงานอื่น ๆ ใน Stack
สองวิธีนี้สามารถอยู่ด้วยกันได้ดีและสนับสนุนซึ่งกันและกันจริง ๆ
การประพันธ์เป็นเพียงการเล่นมันแบบแยกส่วน: คุณสร้างอินเทอร์เฟซที่คล้ายกับระดับผู้ปกครองสร้างวัตถุใหม่และผู้รับมอบสิทธิ์โทรไป ถ้าวัตถุเหล่านี้ไม่จำเป็นต้องรู้ซึ่งกันและกันมันค่อนข้างปลอดภัยและใช้งานง่าย มีความเป็นไปได้มากมายที่นี่
อย่างไรก็ตามถ้าผู้ปกครองระดับด้วยเหตุผลบางอย่างจำเป็นต้องเข้าถึงฟังก์ชั่นที่จัดทำโดย "ระดับเด็ก" สำหรับโปรแกรมเมอร์ที่ไม่มีประสบการณ์มันอาจดูเหมือนว่าเป็นสถานที่ที่ดีในการใช้มรดก ชั้นผู้ปกครองสามารถเรียกมันว่า "foo ()" นามธรรมซึ่งถูกเขียนทับโดยคลาสย่อยและจากนั้นก็สามารถให้ค่ากับฐานนามธรรม
ดูเหมือนว่าเป็นความคิดที่ดี แต่ในหลาย ๆ กรณีมันจะดีกว่าเพียงให้คลาสของวัตถุซึ่งใช้ foo () (หรือแม้แต่ตั้งค่าที่ให้ foo () ด้วยตนเอง) มากกว่าที่จะสืบทอดคลาสใหม่จากคลาสฐานที่ต้องการ ฟังก์ชั่น foo () ที่จะระบุ
ทำไม?
เพราะมรดกเป็นวิธีที่ไม่ดีในการเคลื่อนย้ายข้อมูล
การจัดเรียงนั้นมีความเป็นจริงที่นี่: ความสัมพันธ์สามารถย้อนกลับได้: "ผู้ปกครองระดับ" หรือ "ผู้ทำงานที่เป็นนามธรรม" สามารถรวมวัตถุ "เด็ก" ที่เฉพาะเจาะจงใด ๆ ที่ใช้อินเทอร์เฟซบางอย่าง + เด็ก ๆ มันเป็นประเภทมันประเภท และอาจมีวัตถุจำนวนเท่าใดก็ได้ตัวอย่างเช่น MergeSort หรือ QuickSort สามารถเรียงลำดับรายการของวัตถุใด ๆ ที่ใช้นามธรรมเปรียบเทียบ - อินเทอร์เฟซ หรือใช้วิธีอื่น: กลุ่มวัตถุใด ๆ ที่ใช้ "foo ()" และกลุ่มวัตถุอื่นที่สามารถใช้ประโยชน์จากวัตถุที่มี "foo ()" สามารถเล่นด้วยกันได้
ฉันสามารถนึกถึงสามเหตุผลที่แท้จริงสำหรับการใช้การสืบทอด:
หากสิ่งเหล่านี้เป็นจริงแสดงว่าจำเป็นต้องใช้การสืบทอด
ไม่มีอะไรเลวร้ายในการใช้เหตุผลที่ 1 มันเป็นสิ่งที่ดีมากที่จะมีส่วนต่อประสานที่มั่นคงกับวัตถุของคุณ สิ่งนี้สามารถทำได้โดยใช้การประพันธ์หรือการสืบทอดไม่มีปัญหา - ถ้าส่วนต่อประสานนี้เรียบง่ายและไม่เปลี่ยนแปลง โดยปกติแล้วการสืบทอดจะค่อนข้างมีประสิทธิภาพที่นี่
หากเหตุผลคือหมายเลข 2 มันจะยุ่งยากเล็กน้อย คุณจำเป็นต้องใช้คลาสพื้นฐานเดียวกันเท่านั้นหรือไม่ โดยทั่วไปแล้วการใช้คลาสพื้นฐานเดียวกันนั้นไม่ดีพอ แต่อาจเป็นข้อกำหนดของกรอบการทำงานของคุณการพิจารณาการออกแบบที่ไม่สามารถหลีกเลี่ยงได้
อย่างไรก็ตามหากคุณต้องการใช้ตัวแปรส่วนตัวกรณีที่ 3 คุณอาจมีปัญหา หากคุณพิจารณาตัวแปรส่วนกลางที่ไม่ปลอดภัยคุณควรพิจารณาใช้การสืบทอดเพื่อรับการเข้าถึงตัวแปรส่วนตัวเช่นกัน โปรดทราบว่าตัวแปรระดับโลกไม่ใช่สิ่งที่เลวร้าย - ฐานข้อมูลเป็นชุดตัวแปรระดับโลกที่สำคัญ แต่ถ้าคุณสามารถจัดการกับมันได้มันก็ค่อนข้างดี
หากต้องการตอบคำถามนี้จากมุมมองที่ต่างออกไปสำหรับโปรแกรมเมอร์รุ่นใหม่:
การสืบทอดมักจะถูกสอนตั้งแต่เนิ่นๆเมื่อเราเรียนรู้การเขียนโปรแกรมเชิงวัตถุดังนั้นมันจึงถูกมองว่าเป็นวิธีการแก้ปัญหาที่ง่าย
ฉันมีสามคลาสที่ทุกคนต้องการฟังก์ชั่นทั่วไป ดังนั้นถ้าฉันเขียนคลาสพื้นฐานและให้พวกมันสืบทอดมาพวกมันจะมีฟังก์ชั่นนั้นและฉันจะต้องเก็บมันไว้ในที่เดียว
มันฟังดูยอดเยี่ยม แต่ในทางปฏิบัติมันแทบไม่เคยทำงานเลยด้วยเหตุผลหลายประการ:
ในที่สุดเราผูกรหัสของเราในเงื่อนที่ยากและไม่ได้รับประโยชน์ใด ๆ จากมันยกเว้นว่าเราจะพูดว่า "เยี่ยมยอดฉันเรียนรู้เกี่ยวกับมรดกและตอนนี้ฉันใช้มัน" นั่นไม่ได้หมายความว่าจะวางตัวเพราะเราทำมันเสร็จแล้ว แต่เราทุกคนทำเพราะไม่มีใครบอกให้เราทำ
ทันทีที่มีคนอธิบายว่า "ฉันชอบการแต่งเพลงมากกว่ามรดก" ฉันคิดย้อนกลับไปทุกครั้งที่ฉันพยายามที่จะแบ่งปันฟังก์ชั่นการใช้งานระหว่างชั้นเรียนโดยใช้การสืบทอดและตระหนักว่าส่วนใหญ่มันใช้งานไม่ได้
ยาแก้พิษที่เป็นหลักการ Single รับผิดชอบ คิดว่ามันเป็นข้อ จำกัด ชั้นของฉันต้องทำสิ่งหนึ่ง ฉันจะต้องสามารถให้ชื่อในชั้นเรียนของฉันที่อธิบายอย่างใดอย่างหนึ่งที่มันทำ (มีข้อยกเว้นทุกอย่างมี แต่กฎที่แน่นอนบางครั้งดีกว่าเมื่อเราเรียนรู้ที่กำลัง.) ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses
มันตามที่ฉันไม่สามารถเขียนชั้นฐานที่เรียกว่า ฟังก์ชันการทำงานที่แตกต่างกันที่ฉันต้องการจะต้องอยู่ในคลาสของตัวเองและจากนั้นคลาสอื่น ๆ ที่จำเป็นต้องใช้ฟังก์ชั่นนั้นจะขึ้นอยู่กับคลาสนั้นไม่ใช่สืบทอดมาจากมัน
ความเสี่ยงของการใช้งานเกินขนาดนั่นคือการจัดองค์ประกอบ - การเขียนหลายคลาสเพื่อทำงานร่วมกัน และเมื่อเราสร้างนิสัยนั้นเราพบว่ามันมีความยืดหยุ่นรักษาและทดสอบได้ดีกว่าการใช้มรดก
นอกเหนือจากคือ / มีข้อควรพิจารณาอย่างใดอย่างหนึ่งจะต้องพิจารณา "ความลึก" ของการสืบทอดวัตถุของคุณจะต้องผ่าน อะไรก็ตามที่เกินกว่าห้าหรือหกระดับของการสืบทอดที่ลึกอาจทำให้เกิดปัญหาในการคัดเลือกนักมวยและการชกมวย / การเลิกทำกล่องและในกรณีเหล่านั้นคุณควรประกอบวัตถุของคุณแทน
เมื่อคุณมีis-aความสัมพันธ์ระหว่างสองชั้น (ตัวอย่างเช่นสุนัขสุนัข) คุณไปเป็นมรดก
ในทางตรงกันข้ามเมื่อคุณมี-หรือความสัมพันธ์คำคุณศัพท์ระหว่างสองชั้น (นักเรียนมีหลักสูตร) หรือ (หลักสูตรการศึกษาของครู) คุณเลือกองค์ประกอบ
วิธีง่ายๆในการทำความเข้าใจกับสิ่งนี้ก็คือควรใช้การสืบทอดเมื่อคุณต้องการให้วัตถุของคลาสของคุณมีอินเทอร์เฟซเดียวกับคลาสแม่ของมันเพื่อให้สามารถถือว่าเป็นวัตถุของคลาสแม่ (upcasting) . ยิ่งไปกว่านั้นการเรียกใช้ฟังก์ชันบนอ็อบเจกต์คลาสที่ได้รับจะยังคงเหมือนเดิมทุกที่ในรหัส แต่วิธีการเฉพาะในการโทรจะถูกกำหนดที่รันไทม์ (เช่นการใช้งานในระดับต่ำแตกต่างกันอินเตอร์เฟสระดับสูงยังคงเหมือนเดิม)
ควรใช้องค์ประกอบเมื่อคุณไม่ต้องการคลาสใหม่เพื่อให้มีอินเทอร์เฟซเดียวกันนั่นคือคุณต้องการปกปิดบางแง่มุมของการใช้คลาสซึ่งผู้ใช้ของคลาสนั้นไม่จำเป็นต้องรู้ ดังนั้นองค์ประกอบมากขึ้นในทางของการสนับสนุนการห่อหุ้ม (เช่นปกปิดการดำเนินการ) ในขณะที่มรดกจะหมายถึงการสนับสนุนที่เป็นนามธรรม (เช่นการให้บริการเป็นตัวแทนที่เรียบง่ายของบางสิ่งบางอย่างในกรณีนี้เดียวกันอินเตอร์เฟซสำหรับช่วงของประเภท internals ที่แตกต่างกัน)
การพิมพ์ย่อยมีความเหมาะสมและมีประสิทธิภาพมากกว่าที่สามารถระบุค่า invariants ได้ใช้การจัดองค์ประกอบของฟังก์ชั่นสำหรับการขยาย
ฉันเห็นด้วยกับ @Pavel เมื่อเขาพูดว่ามีสถานที่สำหรับการแต่งเพลงและมีสถานที่สำหรับการสืบทอด
ฉันคิดว่าควรใช้การรับมรดกหากคำตอบของคุณตอบคำถามเหล่านี้
อย่างไรก็ตามหากความตั้งใจของคุณคือการใช้รหัสอย่างหมดจดดังนั้นองค์ประกอบส่วนใหญ่น่าจะเป็นตัวเลือกการออกแบบที่ดีกว่า
การสืบทอดเป็นกลไกที่มีประสิทธิภาพมากสำหรับการนำโค้ดกลับมาใช้ใหม่ แต่จะต้องมีการใช้อย่างถูกต้อง ฉันจะบอกว่ามรดกถูกนำมาใช้อย่างถูกต้องหาก subclass ยังเป็นประเภทย่อยของชั้นผู้ปกครอง ดังกล่าวข้างต้นหลักการทดแทน Liskov เป็นจุดสำคัญที่นี่
คลาสย่อยนั้นไม่เหมือนกับประเภทย่อย คุณอาจสร้างคลาสย่อยที่ไม่ใช่ประเภทย่อย (และนี่คือเวลาที่คุณควรใช้การแต่งเพลง) เพื่อให้เข้าใจว่าประเภทย่อยคืออะไรให้เริ่มอธิบายคำอธิบายของประเภทย่อย
เมื่อเราบอกว่าหมายเลข 5 เป็นจำนวนเต็มชนิดเราจะระบุว่า 5 เป็นชุดของค่าที่เป็นไปได้ (เป็นตัวอย่างให้ดูค่าที่เป็นไปได้สำหรับประเภท Java ดั้งเดิม) นอกจากนี้เรายังระบุว่ามีชุดวิธีการที่ถูกต้องที่ฉันสามารถใช้กับค่าเช่นการบวกและการลบ และในที่สุดเราก็ระบุว่ามีชุดของคุณสมบัติที่พอใจเสมอตัวอย่างเช่นถ้าฉันเพิ่มค่า 3 และ 5 ฉันจะได้ 8 ตามลำดับ
เพื่อให้อีกตัวอย่างหนึ่งคิดเกี่ยวกับประเภทข้อมูลนามธรรมชุดจำนวนเต็มและรายการจำนวนเต็มค่าที่สามารถเก็บได้นั้นถูก จำกัด ไว้ที่จำนวนเต็ม พวกเขาทั้งสองสนับสนุนชุดของวิธีการเช่นเพิ่ม (newValue) และขนาด () และพวกเขาทั้งสองมีคุณสมบัติที่แตกต่างกัน (ค่าคงที่คลาส) ชุดไม่อนุญาตให้ทำซ้ำในขณะที่รายการไม่อนุญาตให้ทำซ้ำ (แน่นอนมีคุณสมบัติอื่น ๆ ที่พวกเขาทั้งสองตอบสนอง)
ชนิดย่อยนั้นยังเป็นชนิดที่มีความสัมพันธ์กับชนิดอื่นที่เรียกว่าประเภทผู้ปกครอง (หรือ supertype) ชนิดย่อยต้องเป็นไปตามคุณลักษณะ (ค่าวิธีการและคุณสมบัติ) ของชนิดพาเรนต์ ความสัมพันธ์หมายถึงว่าในบริบทใดก็ตามที่คาดว่าจะมี supertype มันสามารถทดแทนได้โดย subtype โดยไม่ส่งผลกระทบต่อพฤติกรรมของการดำเนินการ ไปดูรหัสเพื่อเป็นตัวอย่างในสิ่งที่ฉันพูด สมมติว่าฉันเขียนรายการจำนวนเต็ม (ในภาษาหลอกบางชนิด):
class List {
data = new Array();
Integer size() {
return data.length;
}
add(Integer anInteger) {
data[data.length] = anInteger;
}
}
จากนั้นฉันเขียนชุดจำนวนเต็มเป็นคลาสย่อยของรายการจำนวนเต็ม:
class Set, inheriting from: List {
add(Integer anInteger) {
if (data.notContains(anInteger)) {
super.add(anInteger);
}
}
}
ชุดจำนวนเต็มของคลาสของเราเป็นคลาสย่อยของ List of Integers แต่ไม่ใช่ประเภทย่อยเนื่องจากมันไม่เป็นไปตามคุณสมบัติทั้งหมดของ List List ค่าและลายเซ็นของวิธีการเป็นที่พอใจ แต่คุณสมบัติไม่ได้ พฤติกรรมของวิธีการเพิ่ม (จำนวนเต็ม) มีการเปลี่ยนแปลงอย่างชัดเจนไม่ได้รักษาคุณสมบัติของประเภทผู้ปกครอง คิดจากมุมมองของลูกค้าในชั้นเรียนของคุณ พวกเขาอาจได้รับชุดจำนวนเต็มซึ่งคาดว่าจะมีรายการของจำนวนเต็ม ไคลเอ็นต์อาจต้องการเพิ่มค่าและรับค่านั้นถูกเพิ่มเข้าไปในรายการแม้ว่าค่านั้นจะมีอยู่แล้วในรายการ แต่เธอจะไม่ได้รับพฤติกรรมนั้นถ้ามีค่าอยู่ ความประหลาดใจครั้งใหญ่สำหรับเธอ!
นี่เป็นตัวอย่างคลาสสิกของการใช้มรดกอย่างไม่เหมาะสม ใช้องค์ประกอบในกรณีนี้
(ส่วนจาก: ใช้การสืบทอดอย่างถูกต้อง )
กฎของหัวแม่มือที่ฉันเคยได้ยินคือมรดกควรใช้เมื่อความสัมพันธ์และองค์ประกอบ "is-a" ของมันเมื่อ "a-a" ของมัน แม้ว่าฉันจะรู้สึกว่าคุณควรเอนไปทางองค์ประกอบเสมอเพราะมันขจัดความซับซ้อนได้มาก
องค์ประกอบ v / s การสืบทอดเป็นเรื่องที่กว้าง ไม่มีคำตอบที่แท้จริงสำหรับสิ่งที่ดีกว่าเพราะฉันคิดว่ามันทั้งหมดขึ้นอยู่กับการออกแบบของระบบ
โดยทั่วไปประเภทของความสัมพันธ์ระหว่างวัตถุให้ข้อมูลที่ดีกว่าในการเลือกหนึ่งในนั้น
หากความสัมพันธ์ประเภทคือ "IS-A" ความสัมพันธ์แล้วการสืบทอดเป็นวิธีการที่ดีกว่า ประเภทความสัมพันธ์เป็นอย่างอื่นคือ "HAS-A" ความสัมพันธ์แล้วองค์ประกอบจะเข้าใกล้
มันขึ้นอยู่กับความสัมพันธ์เอนทิตี
แม้ว่าองค์ประกอบที่เป็นที่ต้องการผมต้องการจะเน้นข้อดีของมรดกและข้อเสียขององค์ประกอบ
ข้อดีของการสืบทอด:
มันสร้างตรรกะ "ความสัมพันธ์ IS A"หากรถยนต์และรถบรรทุกเป็นยานพาหนะสองประเภท(คลาสพื้นฐาน) คลาสสำหรับเด็กคือคลาสพื้นฐาน
กล่าวคือ
รถยนต์เป็นยานพาหนะ
รถบรรทุกเป็นยานพาหนะ
ด้วยการสืบทอดคุณสามารถกำหนด / แก้ไข / ขยายความสามารถได้
ข้อเสียขององค์ประกอบ:
เช่นหาก รถยนต์มียานพาหนะและหากคุณต้องรับราคารถยนต์ซึ่งกำหนดไว้ในยานพาหนะรหัสของคุณจะเป็นเช่นนี้
class Vehicle{
protected double getPrice(){
// return price
}
}
class Car{
Vehicle vehicle;
protected double getPrice(){
return vehicle.getPrice();
}
}
อย่างที่หลายคนบอกว่าฉันจะเริ่มด้วยการตรวจสอบก่อนว่ามีความสัมพันธ์แบบ "is-a" หรือไม่ หากมีอยู่ฉันมักจะตรวจสอบสิ่งต่อไปนี้:
ไม่ว่าจะเป็นชั้นฐานสามารถยกตัวอย่าง นั่นคือไม่ว่าจะเป็นชั้นฐานสามารถไม่ใช่นามธรรม ถ้าเป็นนามธรรมฉันมักจะชอบองค์ประกอบ
เช่น 1. บัญชีเป็นพนักงาน แต่ฉันจะไม่ใช้การสืบทอดเนื่องจากออบเจกต์พนักงานสามารถสร้างอินสแตนซ์ได้
เช่น 2. หนังสือคือ SellingItem SellingItem ไม่สามารถสร้างอินสแตนซ์ได้ - เป็นแนวคิดที่เป็นนามธรรม ดังนั้นฉันจะใช้ inheritacne SellingItem เป็นคลาสพื้นฐานที่เป็นนามธรรม (หรืออินเตอร์เฟสใน C #)
คุณคิดอย่างไรเกี่ยวกับวิธีการนี้
นอกจากนี้ฉันสนับสนุน @anon คำตอบในเหตุใดจึงใช้การสืบทอดเลย
เหตุผลหลักในการใช้การสืบทอดไม่ได้เป็นรูปแบบของการผสมผสาน - ดังนั้นคุณสามารถรับพฤติกรรมที่หลากหลายได้ หากคุณไม่ต้องการรูปแบบที่หลากหลายคุณอาจไม่ควรใช้การสืบทอด
@MatthieuM พูดว่าใน/software/12439/code-smell-inheritance-abuse/12448#comment303759_12448
ปัญหาเกี่ยวกับการสืบทอดคือสามารถใช้สำหรับวัตถุประสงค์มุมฉากสองจุด:
อินเตอร์เฟส (สำหรับ polymorphism)
การใช้งาน (สำหรับการนำโค้ดกลับมาใช้)
REFERENCE
ฉันไม่เห็นใครพูดถึงปัญหาเพชรซึ่งอาจเกิดขึ้นกับมรดก
ในภาพรวมหากคลาส B และ C สืบทอด A และทั้งวิธีการแทนที่ X และคลาส D ที่สี่สืบทอดมาจากทั้ง B และ C และไม่แทนที่ X ซึ่งการใช้งาน XD แบบใดที่ควรใช้?
Wikipediaเสนอภาพรวมที่ดีของหัวข้อที่กล่าวถึงในคำถามนี้