เหตุใดการใช้ 'ขั้นสุดท้าย' ในชั้นเรียนจึงไม่ดีจริงๆ


34

ฉันกำลังสร้างเว็บไซต์เดิมของ PHP OOP

ฉันถูกล่อลวงให้เริ่มใช้ 'ขั้นสุดท้าย' ในชั้นเรียนถึง " make it explicit that the class is currently not extended by anything" สิ่งนี้อาจช่วยประหยัดเวลาได้มากถ้าฉันมาเรียนและฉันสงสัยว่าฉันสามารถเปลี่ยนชื่อ / ลบ / แก้ไขprotectedคุณสมบัติหรือวิธีการได้หรือไม่ ถ้าฉันจริงๆต้องการที่จะขยายชั้นเรียนฉันก็สามารถลบคำหลักสุดท้ายที่จะปลดล็อคสำหรับการขยาย

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

ทุกอย่างดูเหมือนจะเป็นแนวคิดที่ช่วยประหยัดเวลาได้ แต่ .... ฉันมักจะอ่านว่าคลาสควรจะทำ 'ขั้นสุดท้าย' ในโอกาสที่หายาก / พิเศษเท่านั้น

บางทีมันอาจทำให้เกิดการสร้างวัตถุจำลองหรือมีผลข้างเคียงอื่น ๆ ที่ฉันไม่ได้คิด

ฉันพลาดอะไรไป


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

ตัวอย่างเช่น MbUnit เป็นเฟรมเวิร์กที่เปิดสำหรับการทดสอบหน่วย. Net และ MsTest ไม่เป็นเช่นนั้น เป็นผลให้คุณต้องเปลือกออก 5k เพื่อ MSFT เพียงเพราะคุณต้องการเรียกใช้การทดสอบบางอย่างด้วยวิธี MSFT นักวิ่งทดสอบคนอื่นไม่สามารถรัน dll ทดสอบที่สร้างขึ้นด้วย MsTest - ทั้งหมดเป็นเพราะคลาสสุดท้ายที่ MSFT ใช้
งาน

3
บางบล็อกโพสต์เกี่ยวกับหัวข้อ: คลาสสุดท้าย: เปิดเพื่อการขยาย, ปิดรับมรดก (พฤษภาคม 2014; โดย Mathias Verraes)
hakre

บทความที่ดี มันพูดถึงความคิดของฉันเกี่ยวกับเรื่องนี้ ฉันไม่รู้เกี่ยวกับคำอธิบายประกอบเหล่านั้นดังนั้นจึงมีประโยชน์ ไชโย
JW01

"PHP 5 แนะนำคำสำคัญสุดท้ายซึ่งป้องกันไม่ให้คลาสย่อยของเด็กแทนที่วิธีโดยคำนำหน้าคำจำกัดความด้วยคำนำหน้าหากคลาสตัวเองถูกกำหนดขั้นสุดท้ายแล้วจะไม่สามารถขยายได้" php.net/manual/th/language.oop5.final.phpมันไม่เลวเลย
ดำ

คำตอบ:


54

ฉันมักจะอ่านว่าชั้นเรียนควรทำ 'ขั้นสุดท้าย' ในโอกาสที่หายาก / พิเศษเท่านั้น

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

ดังนั้นการใช้finalโดยค่าเริ่มต้นจะไม่เลว ในความเป็นจริงหลายคนเสนอว่าควรจะเริ่มต้นเช่นจอนสกีต

บางทีมันอาจทำให้เกิดการสร้างวัตถุจำลองขึ้นมา ...

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


1
1+: เนื่องจากมันทำให้เกิดการเยาะเย้ย พิจารณาสร้างอินเทอร์เฟซหรือคลาสนามธรรมสำหรับแต่ละคลาส "ขั้นสุดท้าย"
Spoike

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

1
คุณสามารถพิจารณา Java API เองและความถี่ที่คุณจะพบการใช้คำหลัก 'ขั้นสุดท้าย' จริงๆแล้วมันไม่ได้ใช้บ่อยนัก มันถูกใช้ในกรณีที่ประสิทธิภาพอาจไม่ดีเนื่องจากการเชื่อมโยงแบบไดนามิกเกิดขึ้นเมื่อมีการเรียกวิธีการ "เสมือน" (ใน Java ทุกวิธีจะเป็นเสมือนหากไม่ได้ประกาศว่า 'ขั้นสุดท้าย' หรือ 'ส่วนตัว') หรือเมื่อแนะนำแบบกำหนดเอง คลาสย่อยของคลาส Java API อาจเกิดปัญหาความมั่นคงเช่นคลาสย่อย 'java.lang.String' Java String เป็นวัตถุค่าต่อนิยามและต้องไม่เปลี่ยนค่าในภายหลัง คลาสย่อยสามารถทำลายกฎนี้ได้
จอนนี่ดี

5
@Jonny Java API ส่วนใหญ่ออกแบบมาไม่ดี โปรดจำไว้ว่าส่วนใหญ่มีอายุมากกว่าสิบปีและมีการพัฒนาแนวปฏิบัติที่ดีหลายอย่างในเวลาเดียวกัน แต่ไม่ได้ใช้คำของฉันมัน: วิศวกร Java ตัวเองพูดเพื่อแรกและสำคัญที่สุดจอชโบลชที่ได้เขียนในรายละเอียดเกี่ยวกับเรื่องนี้เช่นในที่มีประสิทธิภาพ Java มั่นใจได้ว่าถ้า Java API ได้รับการพัฒนาในวันนี้ก็จะมีลักษณะมากแตกต่างกันและfinalจะมีบทบาทที่ยิ่งใหญ่กว่า
Konrad Rudolph

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

8

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


17
นี่มันผิดทั้งหมด! “ การใช้คำหลักภาษาเพียงเพื่อส่งสัญญาณบางอย่างให้คุณ […] เป็นความคิดที่ไม่ดี” - ในทางตรงข้ามนั่นคือสิ่งที่คำหลักภาษานั้นมีไว้ให้ ข้อแม้เบื้องต้นของคุณ“ และมีเพียงคุณเท่านั้นที่จะรู้ว่ามันแปลว่าอะไร” แน่นอนว่าถูกต้อง แต่ไม่ได้ใช้ที่นี่ OP กำลังใช้งานfinalอย่างที่คาดไว้ ไม่มีอะไรผิดปกติกับสิ่งนั้น และใช้คุณลักษณะภาษาในการบังคับใช้ข้อ จำกัด คือมักจะดีกว่าที่จะใช้แสดงความคิดเห็น
Konrad Rudolph

8
@ Konrad: ยกเว้นfinalควรแสดงว่า "ไม่มีคลาสย่อยของคลาสนี้ที่ถูกสร้างขึ้น" (สำหรับเหตุผลทางกฎหมายหรือบางสิ่งบางอย่าง) ไม่ใช่ "คลาสนี้ไม่มีลูกในขณะนี้ดังนั้นฉันยังปลอดภัยที่จะยุ่งกับสมาชิกที่ได้รับความคุ้มครอง" จุดประสงค์ของการfinalเป็นสิ่งที่ตรงกันข้ามของ "แก้ไขได้อย่างอิสระ" และfinalชั้นเรียนไม่ควรมีprotectedสมาชิกคนใดเลย!
เอสเอฟ

3
@ Konrad Rudolph มันไม่ได้ย่อเลย หากคนอื่นต้องการขยายชั้นเรียนของเขาในภายหลังมีความแตกต่างอย่างมากระหว่างความคิดเห็นที่ระบุว่า "ไม่ขยาย" และคำหลักที่ระบุว่า "อาจไม่ขยาย"
Jeremy

2
@ Konrad Rudolph ฟังดูเหมือนจะเป็นความคิดที่ยอดเยี่ยมที่จะถามคำถามเกี่ยวกับการออกแบบภาษาของ OOP แต่เรารู้ว่า PHP ทำงานอย่างไรและต้องใช้วิธีการอื่นในการอนุญาตให้มีการขยายได้ ทำท่าว่ามันจะตกลงกันได้กับคำหลักเพราะ "นั่นเป็นวิธีที่ควร" เป็นเพียงการป้องกัน
Jeremy

3
@ Jeremy ฉันไม่ได้ทำให้คำหลักสับสน คำสำคัญfinalหมายถึง“ คลาสนี้จะไม่ขยาย [สำหรับตอนนี้].” ไม่มีอะไรเพิ่มเติมไม่มีอะไรน้อย ไม่ว่าจะเป็น PHP ได้รับการออกแบบด้วยปรัชญาในใจนี้จะไม่เกี่ยวข้อง: มันมีfinalคำหลักหลังจากทั้งหมด ประการที่สองการโต้เถียงจากการออกแบบของ PHP ถูกผูกไว้ที่ล้มเหลวกำหนดวิธีการเย็บปะติดปะต่อกันและโดยรวมเพียงไม่ดี PHP ได้รับการออกแบบ
Konrad Rudolph

5

มีบทความที่ดีเกี่ยวกับการเป็น"เมื่อจะประกาศชั้นเรียนสุดท้าย" คำพูดเล็กน้อยจากมัน:

TL; DR: ทำให้คลาสของคุณเป็นจริงเสมอfinalถ้าใช้อินเทอร์เฟซและไม่มีการกำหนดวิธีสาธารณะอื่น ๆ

ทำไมฉันต้องใช้final?

  1. ป้องกันไม่ให้ห่วงโซ่การสืบทอดขนาดใหญ่ของการลงโทษ
  2. ส่งเสริมองค์ประกอบ
  3. บังคับให้นักพัฒนาคิดเกี่ยวกับ API สาธารณะของผู้ใช้
  4. บังคับให้ผู้พัฒนาย่อขนาด API สาธารณะของวัตถุ
  5. finalระดับจะสามารถทำให้ขยาย
  6. extends แบ่ง encapsulation
  7. คุณไม่ต้องการความยืดหยุ่นนั้น
  8. คุณมีอิสระที่จะเปลี่ยนรหัส

ควรหลีกเลี่ยงเมื่อใดfinal:

คลาสสุดท้ายใช้งานได้อย่างมีประสิทธิภาพภายใต้สมมติฐานดังต่อไปนี้:

  1. มีสิ่งที่เป็นนามธรรม (อินเทอร์เฟซ) ที่คลาสสุดท้ายใช้
  2. API สาธารณะทั้งหมดของคลาสสุดท้ายเป็นส่วนหนึ่งของอินเทอร์เฟซนั้น

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

ป.ล. ขอบคุณ @ocramius สำหรับการอ่านที่ยอดเยี่ยม!


5

"final" สำหรับคลาสหมายถึง: คุณต้องการคลาสย่อยหรือไม่? ไปข้างหน้าลบ "ขั้นสุดท้าย" คลาสย่อยได้มากเท่าที่คุณต้องการ แต่อย่าบ่นกับฉันถ้ามันไม่ทำงาน คุณอยู่คนเดียว

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


-1

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

อย่าทำเครื่องหมายสิ่งต่าง ๆ ว่าเป็นที่สุดเว้นแต่คุณจะหมายความว่าอย่างนั้นจริงๆ


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

4
ฉันจะพูดแบบนี้มากขึ้นอย่างยิ่ง อย่าทำเครื่องหมายเป็นสิ่งสุดท้ายยกเว้นว่าส่วนขยายไม่สามารถทำงานได้เนื่องจากการออกแบบของชั้นเรียน
S.Lott

ขอบคุณ S.Lott - ฉันพยายามเข้าใจว่าการใช้งานขั้นสุดท้ายที่ไม่ดีส่งผลต่อรหัส / โครงการอย่างไร คุณมีประสบการณ์เรื่องราวสยองขวัญที่ไม่ดีใด ๆ finalที่ได้นำคุณที่จะให้ดันทุรังเกี่ยวกับการใช้ประหยัดของ นี่เป็นประสบการณ์มือแรกหรือไม่?
JW01

1
@ JW01: finalคลาสมีหนึ่งกรณีการใช้งานหลัก คุณมีคลาส polymorphic ที่คุณไม่ต้องการขยายเนื่องจากคลาสย่อยอาจทำลาย polymorphism อย่าใช้finalจนกว่าคุณจะต้องป้องกันการสร้างคลาสย่อย นอกจากนั้นมันไร้ประโยชน์
S.Lott

@ JW01 ในการตั้งค่าการผลิตคุณอาจไม่ได้รับอนุญาตให้ลบขั้นสุดท้ายเนื่องจากนี่ไม่ใช่รหัสที่ลูกค้าทดสอบและอนุมัติ อย่างไรก็ตามคุณอาจได้รับอนุญาตให้เพิ่มรหัสพิเศษ (สำหรับการทดสอบ) แต่คุณอาจไม่สามารถทำสิ่งที่คุณต้องการเพราะคุณไม่สามารถขยายชั้นเรียนได้

-1

การใช้ 'ขั้นสุดท้าย' จะช่วยให้ผู้อื่นมีอิสระในการใช้งานโค้ดของคุณ

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

นอกจากนี้ยังมีมักจะเป็นรหัสที่ควรจะดีกว่าไม่ได้ทำแต่private protectedแน่นอนว่าprivateหมายถึง "encapsulation" และซ่อนสิ่งที่ถือว่าเป็นรายละเอียดการใช้งาน แต่ในฐานะที่เป็นโปรแกรมเมอร์ API ฉันก็อาจบันทึกความจริงที่ว่าวิธีการxyzนั้นถูกพิจารณาว่าเป็นรายละเอียดการใช้งานและดังนั้นจึงอาจมีการเปลี่ยนแปลง / ลบในเวอร์ชันอนาคต ดังนั้นทุกคนที่จะพึ่งพารหัสดังกล่าวทั้งๆที่คำเตือนกำลังทำมันอยู่กับความเสี่ยงของเขาเอง แต่จริง ๆ แล้วเขาสามารถทำได้และนำโค้ดกลับมาใช้ใหม่ (หวังว่าทดสอบแล้ว) และหาวิธีแก้ปัญหาได้เร็วขึ้น

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

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

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


2
"การสืบทอดไม่ใช่การใช้รหัสซ้ำ"
quant_dev

2
ตกลงถ้าคุณยืนยันในคำจำกัดความที่สมบูรณ์แบบคุณพูดถูก การถ่ายทอดทางพันธุกรรมเป็นการแสดงออกถึงความสัมพันธ์ 'is-a' และมันเป็นความจริงนี้ที่ช่วยให้มีความแตกต่างและให้วิธีการขยาย API แต่ในทางปฏิบัติแล้วการสืบทอดมักใช้เพื่อนำรหัสมาใช้ซ้ำ โดยเฉพาะอย่างยิ่งกรณีที่มีการสืบทอดหลายรายการ และทันทีที่คุณเรียกรหัสของซุปเปอร์คลาสคุณจะใช้รหัสซ้ำ
จอนนี่ดี

@quant_dev: การใช้ซ้ำได้ใน OOP หมายถึงอันดับแรกของโพลีเมอร์ทั้งหมด และการสืบทอดเป็นจุดสำคัญสำหรับพฤติกรรม polymorphic (การแบ่งปันอินเตอร์เฟส)
Falcon

@Falcon อินเตอร์เฟสที่ใช้ร่วมกัน! = รหัสการแชร์ อย่างน้อยก็ไม่ใช่ในความหมายปกติ
quant_dev

3
@quant_dev: คุณได้รับการใช้ซ้ำในความรู้สึกผิด OOP มันหมายความว่าวิธีการเดียวสามารถทำงานบนหลายประเภทข้อมูลขอบคุณที่ใช้งานร่วมกันของอินเตอร์เฟซ นั่นเป็นส่วนสำคัญของการใช้รหัส OOP ซ้ำ และการสืบทอดโดยอัตโนมัติให้เราแบ่งปันส่วนต่อประสานซึ่งช่วยเพิ่มการใช้ซ้ำได้อย่างมาก นั่นเป็นเหตุผลที่เราพูดคุยเกี่ยวกับการใช้ซ้ำใน OOP เพียงแค่สร้างไลบรารีที่มีรูทีนเกือบจะเท่ากับการเขียนโปรแกรมและสามารถทำได้กับเกือบทุกภาษามันเป็นเรื่องธรรมดา การใช้ซ้ำรหัสใน OOP เป็นมากกว่านั้น
เหยี่ยว
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.