ทำไมต้องปิดชั้นเรียน?


96

ฉันต้องการทราบว่าอะไรคือแรงจูงใจเบื้องหลังคลาสปิดผนึกจำนวนมากในกรอบงาน. Net การผนึกชั้นเรียนมีประโยชน์อย่างไร? ฉันไม่สามารถเข้าใจได้ว่าการไม่ยอมให้มรดกมีประโยชน์อย่างไรและส่วนใหญ่ไม่ใช่คนเดียวที่ต่อสู้กับคลาสเหล่านี้

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



คำตอบ:


41
  • บางครั้งชั้นเรียนมีค่ามากเกินไปและไม่ได้ออกแบบมาเพื่อสืบทอด
  • Runtime / Reflection สามารถสร้างสมมติฐานการสืบทอดเกี่ยวกับคลาสที่ปิดผนึกเมื่อมองหาประเภท ตัวอย่างที่ดีคือ - แนะนำให้ปิดผนึกแอตทริบิวต์เพื่อความเร็วรันไทม์ในการค้นหา type.GetCustomAttributes (typeof (MyAttribute)) จะทำงานได้เร็วขึ้นอย่างมากหาก MyAttribute ถูกปิดผนึก

บทความ MSDN สำหรับหัวข้อนี้ถูกจำกัด การขยายชั้นเรียนโดยการปิดผนึก


3
ดีใจที่เห็นพวกเขาพูดชัด ๆ ว่า "ใช้อย่างระมัดระวัง" ตอนนี้ ... หวังว่าพวกเขาจะปฏิบัติตามที่พวกเขาเทศน์
mmiika

13
นั่นดูเหมือนคำแนะนำที่ไม่ดีสำหรับฉัน :(
Jon Skeet

4
@CVertex: ขออภัยฉันไม่ได้พยายามวิพากษ์วิจารณ์คุณ - แค่บทความ
Jon Skeet

16
@generalt: ฉันเชื่อในการออกแบบเพื่อการสืบทอดหรือห้ามมัน การออกแบบเพื่อการสืบทอดใช้เวลาไม่นานและมักจะ จำกัด การนำไปใช้ในอนาคต การถ่ายทอดทางพันธุกรรมยังนำเสนอความไม่แน่นอนให้กับผู้โทรว่าพวกเขาจะโทรไปหาอะไร มันยังไม่เข้ากันกับความไม่เปลี่ยนรูป (ซึ่งฉันเป็นแฟนคลับ) ฉันพบว่าการสืบทอดคลาสมีประโยชน์ในสถานที่ค่อนข้างน้อย (ในขณะที่ฉันชอบอินเทอร์เฟซ)
Jon Skeet

1
@CVertex หากคุณเคยใช้. NET คุณอาจพบปัญหานี้และไม่ได้สังเกตเห็นคลาสหลักของ. NET เกือบทั้งหมดจะถูกปิดผนึก
CoryG

105

ชั้นเรียนควรได้รับการออกแบบมาเพื่อการสืบทอดหรือห้าม มีค่าใช้จ่ายในการออกแบบสำหรับการสืบทอด:

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

รายการที่ 17 ของ Effective Java จะกล่าวถึงรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องนี้ - ไม่ว่าจะเขียนในบริบทของ Java คำแนะนำจะใช้กับ. NET ด้วยเช่นกัน

ส่วนตัวฉันต้องการให้คลาสถูกปิดผนึกโดยค่าเริ่มต้นใน. NET


26
อืม .. ถ้าคุณขยายชั้นเรียนมันจะเป็นปัญหาของคุณหรือเปล่า
mmiika

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

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

8
@ Joan: องค์ประกอบคือความสัมพันธ์แบบ "has-a" แทนที่จะเป็น "is-a" ดังนั้นหากคุณต้องการเขียนคลาสที่สามารถทำหน้าที่เหมือนลิสต์ได้ในบางวิธี แต่ไม่ใช่ในคลาสอื่นคุณอาจต้องการสร้างคลาสที่มีตัวแปรสมาชิก List <T> แทนที่จะได้มาจาก List <T> จากนั้นคุณจะใช้รายการเพื่อใช้วิธีการต่างๆ
Jon Skeet

3
@ThunderGr: แต่นั่นคือประเด็นของฉัน: เมื่อคุณไม่จำเป็นต้องกังวลเกี่ยวกับการใช้งานอื่น ๆ ที่มีให้โดยคลาสย่อยคุณจะมีอิสระมากขึ้นด้วยการใช้งานของคุณเอง ตัวอย่างเช่นสมมติว่าวิธีหนึ่งถูกนำไปใช้โดยการเรียกใช้วิธีอื่นและทั้งสองเป็นเสมือน ที่ต้องจัดทำเป็นเอกสาร - แม้ว่าจะรู้สึกเหมือนเป็นรายละเอียดการใช้งานก็ตาม โดยพื้นฐานแล้วการออกแบบเพื่อการสืบทอดจะเพิ่มกุญแจมือซึ่งเหมาะสมในบางกรณี แต่ไม่ใช่ในกรณีอื่น ฉันอยากจะมีคลาสปิดผนึกที่ใช้อินเทอร์เฟซมากกว่าคลาสที่ปิดผนึกด้วยวิธีการเสมือนจริงมากมาย
Jon Skeet

8

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

X อย่าปิดผนึกชั้นเรียนโดยไม่มีเหตุผลที่ดีในการทำเช่นนั้น

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

เหตุผลที่ดีในการปิดผนึกชั้นเรียนมีดังต่อไปนี้:

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

X อย่าประกาศสมาชิกที่ได้รับการป้องกันหรือเสมือนในประเภทที่ปิดผนึก

ตามความหมายแล้วประเภทที่ปิดผนึกไม่สามารถสืบทอดมาได้ ซึ่งหมายความว่าไม่สามารถเรียกสมาชิกที่ได้รับการป้องกันในชนิดที่ปิดผนึกและไม่สามารถลบล้างเมธอดเสมือนบนชนิดปิดผนึก

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

อันที่จริงหากคุณค้นหา ASP.Net Core codebaseคุณจะพบเพียง 30 ครั้งsealed classเท่านั้นซึ่งส่วนใหญ่เป็นแอตทริบิวต์และคลาสทดสอบ

ฉันคิดว่าการอนุรักษ์ความไม่เปลี่ยนรูปเป็นข้อโต้แย้งที่ดีในการปิดผนึก


4

ฉันพบประโยคนี้ในเอกสาร msdn: "คลาสปิดผนึกส่วนใหญ่จะใช้เพื่อป้องกันการสืบทอดเนื่องจากไม่สามารถใช้เป็นคลาสพื้นฐานได้

ฉันไม่รู้ว่าการแสดงเป็นข้อดีเพียงอย่างเดียวของคลาสปิดผนึกหรือเปล่าและโดยส่วนตัวฉันก็อยากรู้เหตุผลอื่น ๆ ด้วย ...


4
จะน่าสนใจขนาดไหนที่พวกเขาพูดถึง ...
mmiika

3

ประสิทธิภาพเป็นปัจจัยสำคัญเช่นคลาสสตริงใน java ถือเป็นขั้นสุดท้าย (<- ปิดผนึก) และเหตุผลนี้เป็นเพียงประสิทธิภาพเท่านั้น ฉันคิดว่าจุดสำคัญอีกประการหนึ่งคือการหลีกเลี่ยงปัญหาคลาสฐานที่เปราะซึ่งอธิบายไว้ในรายละเอียดที่นี่ http://blogs.msdn.com/ericlippert/archive/2004/01/07/virtual-methods-and-brittle-base-classes aspx

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


สาเหตุที่ทำให้ String ใน java เป็นขั้นสุดท้ายไม่ใช่ประสิทธิภาพ แต่เป็นการรักษาความปลอดภัย
CesarB

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


0

การปิดผนึกช่วยให้คุณทราบถึงประสิทธิภาพที่เพิ่มขึ้นเล็กน้อย นี่เป็นความจริงน้อยกว่าในโลกของ JITs และการมองโลกในแง่ร้ายอย่างขี้เกียจมากกว่าในโลกของ C ++ แต่เนื่องจาก. NET ไม่ดีเท่าการมองโลกในแง่ร้ายเนื่องจากคอมไพเลอร์ java ส่วนใหญ่เป็นเพราะปรัชญาการออกแบบที่แตกต่างกันจึงยังมีประโยชน์ มันบอกคอมไพลเลอร์ว่าสามารถเรียกใช้เมธอดเสมือนได้โดยตรงแทนที่จะเรียกโดยอ้อมผ่าน vtable

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



0

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

•ประโยชน์ที่อาจเกิดขึ้นจากการเรียนในชั้นเรียนอาจได้รับจากความสามารถในการปรับแต่งชั้นเรียนของคุณ

•ศักยภาพที่คลาสที่ได้มาสามารถปรับเปลี่ยนคลาสของคุณในลักษณะที่พวกเขาจะไม่ทำงานอย่างถูกต้องหรือตามที่คาดไว้อีกต่อไป


0

ข้อควรพิจารณาเพิ่มเติมคือคลาสที่ปิดผนึกไม่สามารถถูกตรึงในการทดสอบหน่วยของคุณได้ จากเอกสารของ Microsoft :

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

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