เหตุผลที่ดีที่จะห้ามการสืบทอดใน Java?


178

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


1
Pedantry: ตัวสร้างเริ่มต้นคือสิ่งที่สร้างขึ้นสำหรับคุณโดยคอมไพเลอร์ Java หากคุณไม่ได้เขียนไว้ในโค้ดอย่างชัดเจน คุณหมายถึงตัวสร้างอาร์กิวเมนต์ที่ไม่มี
John Topley

นั่นไม่ใช่ "ตัวสร้างปริยายเริ่มต้น" หรือไม่
cretzel

2
"คุณไม่จำเป็นต้องสร้าง constructor ใด ๆ สำหรับคลาสของคุณ แต่คุณต้องระวังในการทำเช่นนี้คอมไพเลอร์จะจัดให้ไม่มีคอนสตรัคเตอร์ปริยายโดยอัตโนมัติสำหรับคลาสใด ๆ ที่ไม่มีตัวสร้าง" java.sun.com/docs/books/tutorial/java/javaOO/constructors.html - John Topley
John Topley

คำตอบ:


184

การอ้างอิงที่ดีที่สุดของคุณที่นี่คือรายการที่ 19 ของหนังสือยอดเยี่ยมของ Joshua Bloch "Effective Java" ที่เรียกว่า "การออกแบบและเอกสารสำหรับการสืบทอดไม่เช่นนั้นจะห้าม" (เป็นรายการที่ 17 ในรุ่นที่สองและรายการที่ 15 ในรุ่นแรก) คุณควรอ่านจริง ๆ แต่ฉันจะสรุป

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

คลาสจึงควรมีสองแบบ:

  1. คลาสที่ออกแบบมาเพื่อขยายและมีเอกสารเพียงพอที่จะอธิบายวิธีการทำ

  2. คลาสที่ทำเครื่องหมายสุดท้าย

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


6
จากประสบการณ์ของฉันนี่ไม่ใช่ overkill ถ้าใครนอกจากฉันจะใช้รหัส ฉันไม่เคยเข้าใจว่าทำไม Java มีวิธีการ overridable โดยค่าเริ่มต้น
Eric Weilnau

9
เพื่อให้เข้าใจ Java ที่มีประสิทธิภาพบางตัวคุณต้องเข้าใจการออกแบบของ Josh เขาบอกว่า [สิ่งที่ชอบ] คุณควรออกแบบอินเทอร์เฟซสำหรับชั้นเรียนเหมือนเป็น API สาธารณะ ฉันคิดว่าความเห็นส่วนใหญ่คือวิธีนี้มักจะหนักเกินไปและไม่ยืดหยุ่น (YAGNI ฯลฯ )
Tom Hawtin - tackline

9
คำตอบที่ดี. (ฉันไม่สามารถต้านทานชี้ให้เห็นว่ามันเป็นตัวละครหกตัวเพราะยังมีช่องว่าง ... ;-)
joel.neely

36
โปรดทราบว่าการทำให้ชั้นเรียนเป็นขั้นสุดท้ายอาจทำให้การทดสอบยากขึ้น (ยากที่จะเริ่มต้นหรือล้อเลียน)
notnoop

10
หากคลาสสุดท้ายเขียนไปยังอินเตอร์เฟสการเยาะเย้ยไม่น่าจะมีปัญหา
Fred Haslam

26

คุณอาจต้องการทำให้วิธีการสุดท้ายเพื่อให้การเอาชนะชั้นเรียนไม่สามารถเปลี่ยนพฤติกรรมที่นับในวิธีอื่น ๆ วิธีการที่เรียกใน Constructor นั้นมักถูกประกาศให้ทราบขั้นสุดท้ายดังนั้นคุณจะไม่ได้รับความประหลาดใจอันไม่พึงประสงค์เมื่อสร้างวัตถุ


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

1
@Nav หากคุณทำให้วิธีการสุดท้ายแล้วชั้นเรียนที่เอาชนะจะไม่สามารถมีรุ่นของตัวเองของวิธีการนั้น (หรือมันจะเป็นข้อผิดพลาดในการรวบรวม) ด้วยวิธีนี้คุณจะรู้ว่าพฤติกรรมที่คุณใช้ในวิธีนั้นจะไม่เปลี่ยนแปลงในภายหลังเมื่อมีคนอื่นขยายชั้นเรียน
Bill the Lizard

19

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


15

มี 3 กรณีการใช้งานที่คุณสามารถไปหาวิธีสุดท้ายได้

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

จุดประสงค์ในการทำให้ชั้นเรียนสุดท้าย:

เพื่อให้ร่างกายไม่สามารถขยายชั้นเรียนเหล่านั้นและเปลี่ยนพฤติกรรมของพวกเขา

เช่นคลาส Wrapper Integer เป็นคลาสสุดท้าย หากคลาสนั้นไม่เป็นที่สิ้นสุดดังนั้นบุคคลใดก็ตามสามารถขยายจำนวนเต็มเป็นคลาสของตนเองและเปลี่ยนพฤติกรรมพื้นฐานของคลาสจำนวนเต็ม เพื่อหลีกเลี่ยงปัญหานี้ java จึงสร้างคลาส wrapper ทั้งหมดเป็นคลาสสุดท้าย


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

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

3
> หากคลาสนั้นไม่เป็นที่สิ้นสุดดังนั้นบุคคลใดก็ตามสามารถขยายจำนวนเต็มลงในคลาสของตนเองและเปลี่ยนพฤติกรรมพื้นฐานของคลาสจำนวนเต็ม สมมติว่าได้รับอนุญาตและมีการเรียกคลาสใหม่นี้DerivedIntegerก็ยังไม่เปลี่ยนคลาส Integer ดั้งเดิมและใครก็ตามที่ใช้งานDerivedIntegerจะต้องรับความเสี่ยงด้วยตัวเองดังนั้นฉันจึงยังไม่เข้าใจสาเหตุที่เป็นปัญหา
Siddhartha

12

คุณอาจต้องการสร้างวัตถุที่ไม่เปลี่ยนรูปแบบ ( http://en.wikipedia.org/wiki/Immutable_object ) คุณอาจต้องการสร้างซิงเกิลตัน ( http://en.wikipedia.org/wiki/Singleton_pattern ) หรือคุณอาจต้องการ เพื่อป้องกันไม่ให้ใครบางคนเอาชนะวิธีการด้วยเหตุผลของประสิทธิภาพความปลอดภัยหรือความปลอดภัย


7

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

ดูผลบังคับใช้ Java รายการรุ่นที่ 2 วันที่ 16 และ 17 หรือโพสต์บล็อกของฉัน"ภาษีมรดก"


ลิงก์ไปยังโพสต์บล็อกเสีย คุณคิดว่าคุณสามารถอธิบายเพิ่มเติมเกี่ยวกับเรื่องนี้ได้อีกเล็กน้อยหรือไม่?

@MichaelT: แก้ไขลิงก์ขอบคุณ - ทำตามเพื่อขอรายละเอียดเพิ่มเติม :) (ฉันไม่มีเวลาเพิ่มตอนนี้ฉันเกรงว่า)
Jon Skeet

@JonSkeet ลิงก์ไปยังบล็อกโพสต์เสีย
ลูซิเฟอร์

@Kedarnath: มันทำงานสำหรับฉัน ... มันถูกเสียในขณะที่ แต่ตอนนี้ที่ชี้ไปยังหน้าที่ถูกต้องใน codeblog.jonskeet.uk ...
จอนสกีต

4

อืม ... ฉันนึกถึงสองสิ่ง:

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

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

 String blah = someOtherString;

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


2

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


2

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


1
ฉันเชื่อว่านี่เป็นตำนานเนื่องจาก VM ยุคปัจจุบันควรสามารถปรับให้เหมาะสมและลดความต้องการได้ตามต้องการ ฉันจำได้ว่าเคยเห็นการเปรียบเทียบนี้และจำแนกมันเป็นตำนาน
Miguel Ping

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

1
มันไม่ได้เป็นตำนาน ในความเป็นจริงผู้แปลสามารถ inline V วิธีสุดท้ายเท่านั้น ฉันไม่แน่ใจว่าสิ่งใดที่ JIT "แบบอินไลน์" เป็นงานศิลปะรันไทม์ แต่ฉันสงสัยว่าสามารถทำได้เนื่องจากคุณอาจโหลดคลาสที่ได้รับในภายหลัง
Uri

1
Microbenchmark == เมล็ดของเกลือ ที่กล่าวว่าหลังจากการวอร์มอัพ 10B รอบการรับสายมากกว่า 10B แสดงว่าไม่มีความแตกต่างภายใต้ Eclipse บนแล็ปท็อปของฉัน สองวิธีที่ส่งกลับสตริงสุดท้ายคือ 6.9643us ไม่ใช่ตอนสุดท้ายคือ 6.9641us เดลต้านั่นคือเสียงรบกวนพื้นหลัง IMHO
joel.neely

1

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


2
มีบางอย่างที่ฉันไม่เข้าใจเกี่ยวกับข้อโต้แย้งนั้น - ถ้ามีคนเปลี่ยนการคำนวณ / ค่าคงที่ที่ไม่ควรเปลี่ยน - ไม่ใช่ความล้มเหลวใด ๆ เนื่องจากความผิด 100% นั้นหรือไม่ กล่าวอีกนัยหนึ่งการเปลี่ยนแปลงมีประโยชน์อย่างไร?
matt b

ใช่มันเป็นความผิดพลาดทางเทคนิค "ของพวกเขา" แต่ลองนึกภาพคนอื่นที่ใช้ห้องสมุดที่ไม่ได้รับรู้ถึงการเปลี่ยนแปลงอาจทำให้เกิดความสับสน ฉันคิดเสมอว่านั่นเป็นเหตุผลที่คลาส Java Base หลายคลาสถูกทำเครื่องหมายเป็นขั้นสุดท้ายเพื่อหยุดคนที่เปลี่ยนฟังก์ชั่นพื้นฐาน
Anson Smith

@matt b - คุณดูเหมือนจะสมมติว่าผู้พัฒนาที่ทำการเปลี่ยนแปลงทำอย่างรู้เท่าทันหรือพวกเขาจะสนใจว่ามันเป็นความผิดของพวกเขา หากมีใครบางคนนอกเหนือจากฉันจะใช้รหัสของฉันฉันจะทำเครื่องหมายเป็นครั้งสุดท้ายเว้นแต่ว่าจะมีการเปลี่ยนแปลง
Eric Weilnau

นี่คือการใช้ 'สุดท้าย' ที่แตกต่างจากที่เคยถาม
DJClayworth

คำตอบนี้ดูเหมือนจะสร้างความสับสนให้กับการสืบทอดและความไม่แน่นอนของแต่ละฟิลด์ด้วยความสามารถในการเขียนคลาสย่อย คุณสามารถทำเครื่องหมายแต่ละฟิลด์และวิธีการเป็นขั้นสุดท้าย (ป้องกันการกำหนดใหม่) โดยไม่ต้องห้ามการสืบทอด
joel.neely

0

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

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