ทำไมภาษา OOP แบบคงที่ที่สำคัญจึงป้องกันการสืบทอดดั้งเดิม


53

ทำไมตกลงนี้และคาดว่าส่วนใหญ่:

abstract type Shape
{
   abstract number Area();
}

concrete type Triangle : Shape
{
   concrete number Area()
   {
      //...
   }
}

... ขณะนี้ไม่เป็นไรและไม่มีใครบ่น:

concrete type Name : string
{
}

concrete type Index : int
{
}

concrete type Quantity : int
{
}

แรงจูงใจของฉันคือการเพิ่มการใช้ระบบประเภทเพื่อการตรวจสอบความถูกต้องเวลารวบรวม

PS: ใช่ฉันได้อ่านเรื่องนี้แล้วการห่อเป็นงานที่ยุ่งเหยิง


1
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
maple_shaft

ฉันมีแรงจูงใจคล้ายกันในคำถามนี้คุณอาจพบว่ามันน่าสนใจ
default.kramer

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

คำตอบ:


83

ฉันคิดว่าคุณกำลังคิดถึงภาษาอย่าง Java และ C # ใช่ไหม

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

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

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

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

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

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


12
@Den: stringถูกปิดผนึกเพราะมันถูกออกแบบมาให้ทำงานไม่เปลี่ยนรูป หากมีใครสามารถสืบทอดจากสตริงมันจะเป็นไปได้ที่จะสร้างสตริงที่ไม่แน่นอนซึ่งจะทำให้เกิดข้อผิดพลาดได้ง่าย รหัสมากมายรวมถึง. NET Framework นั้นขึ้นอยู่กับสตริงที่ไม่มีผลข้างเคียง ดูที่นี่บอกเหมือนกัน: quora.com/Why-String-class-in-C-is-a-sealed-class
Doc Brown

5
@DocBrown นี่คือเหตุผลที่Stringถูกทำเครื่องหมายfinalใน Java เช่นกัน
Dev

47
"เมื่อ Java ถูกออกแบบ Smalltalk ถูกพิจารณาว่าช้าเกินไป […]. Java เสียสละความสง่างามและความบริสุทธิ์ตามแนวคิดเพื่อให้ได้ประสิทธิภาพที่ดีขึ้น" - แน่นอนว่าจาวาไม่ได้รับผลการปฏิบัติงานจริงจนกว่า Sun จะซื้อ บริษัท Smalltalk เพื่อเข้าถึงเทคโนโลยี Smalltalk VM เพราะ JVM ของตนเองของ Sun ช้าลงและปล่อย HotSpot JVM ซึ่งเป็น Smalltalk VM ที่ปรับเปลี่ยนเล็กน้อย
Jörg W Mittag

3
@underscore_d: คำตอบที่คุณเชื่อมโยงไปมากอย่างชัดเจนระบุว่าC♯ไม่ได้มีรูปแบบดั้งเดิม แน่นอนว่าบางแพลตฟอร์มที่มีการนำC♯ไปใช้อาจมีหรือไม่มีประเภทดั้งเดิม แต่นั่นไม่ได้หมายความว่าC♯มีประเภทดั้งเดิม เช่นมีการนำไปใช้ของ Ruby สำหรับ CLI และ CLI มีประเภทดั้งเดิม แต่ไม่ได้หมายความว่า Ruby มีประเภทดั้งเดิม การนำไปใช้อาจหรือไม่เลือกที่จะใช้ชนิดของค่าโดยการแมปกับชนิดดั้งเดิมของแพลตฟอร์ม แต่นั่นเป็นรายละเอียดการนำไปใช้ภายในแบบส่วนตัวและไม่ใช่ส่วนหนึ่งของข้อมูลจำเพาะ
Jörg W Mittag

10
มันเป็นเรื่องของนามธรรม เราต้องทำให้สมองของเราปลอดโปร่งไม่งั้นก็จบลงด้วยเรื่องไร้สาระ ตัวอย่างเช่น: C♯ใช้งานบน. NET มีการใช้. NET บน Windows NT มีการใช้งาน Windows NT ใน x86 x86 นำมาใช้กับซิลิโคนไดออกไซด์ SiO₂เป็นเพียงทราย ดังนั้นstringในC♯เป็นเพียงทราย ไม่แน่นอนไม่ใช่stringในC♯คือสิ่งที่C♯ spec บอกว่าเป็น วิธีการนำไปใช้นั้นไม่เกี่ยวข้อง การติดตั้ง C native แบบดั้งเดิมจะใช้สตริงเป็นอาร์เรย์ไบต์การใช้ ECMAScript จะจับคู่กับ ECMAScript Strings และอื่น ๆ
Jörg W Mittag

20

ภาษาอะไรบางอย่างที่นำเสนอไม่ได้ subclassing แต่subtyping ยกตัวอย่างเช่น Ada ช่วยให้คุณสร้างมาประเภทหรือชนิดย่อย ส่วนAda Programming / Type Systemนั้นคุ้มค่าที่จะอ่านเพื่อทำความเข้าใจรายละเอียดทั้งหมด คุณสามารถ จำกัด ช่วงของค่าซึ่งเป็นสิ่งที่คุณต้องการมากที่สุด:

 type Angle is range -10 .. 10;
 type Hours is range 0 .. 23; 

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

 type Reference is Integer;
 type Count is Integer;

ประเภทข้างต้นเข้ากันไม่ได้แม้ว่าจะเป็นช่วงที่มีค่าเหมือนกันก็ตาม

(แต่คุณสามารถใช้ Unchecked_Conversion; ไม่บอกคนที่ฉันบอกคุณ)


2
ที่จริงฉันคิดว่ามันเป็นเรื่องเกี่ยวกับความหมาย การใช้ปริมาณที่คาดว่าดัชนีจะทำให้เกิดข้อผิดพลาดในเวลารวบรวม
Marjan Venema

@MarjanVenema มันทำและสิ่งนี้จะทำเพื่อวัตถุประสงค์ในการตรวจจับข้อผิดพลาดเชิงตรรกะ
coredump

ประเด็นของฉันคือไม่ใช่ทุกกรณีที่คุณต้องการซีแมนทิกส์ แต่คุณต้องมีช่วง คุณจะมีtype Index is -MAXINT..MAXINT;วิธีใดที่จะไม่ทำอะไรให้ฉันเลยเพราะจำนวนเต็มทั้งหมดจะใช้ได้? ดังนั้นฉันจะเกิดข้อผิดพลาดชนิดใดในการส่งมุมไปยังดัชนีหากสิ่งที่ตรวจสอบทั้งหมดเป็นช่วง?
Marjan Venema

1
@MarjanVenema ในตัวอย่างที่สองของเธอทั้งสองประเภทเป็นชนิดย่อยของจำนวนเต็ม อย่างไรก็ตามถ้าคุณประกาศฟังก์ชั่นที่ยอมรับการนับคุณจะไม่สามารถผ่านการอ้างอิงได้เนื่องจากการตรวจสอบประเภทขึ้นอยู่กับการเทียบชื่อซึ่งตรงกันข้ามกับ "ทั้งหมดที่มีการตรวจสอบเป็นช่วง" สิ่งนี้ไม่ จำกัด จำนวนเต็มคุณสามารถใช้ชนิดหรือระเบียนที่ระบุ ( archive.adaic.com/standards/83rat/html/ratl-04-03.html )
coredump

1
@Marjan ตัวอย่างหนึ่งที่ดีของว่าทำไมประเภทการติดแท็กสามารถที่มีประสิทธิภาพค่อนข้างสามารถพบได้ในเอริค Lippert ของซีรีส์เกี่ยวกับการใช้ Zorg ใน OCaml การทำเช่นนี้ทำให้คอมไพเลอร์ตรวจจับข้อบกพร่องมากมาย - ในทางกลับกันหากคุณอนุญาตให้แปลงชนิดโดยปริยายสิ่งนี้ดูเหมือนจะทำให้คุณสมบัติไร้ประโยชน์ .. มันไม่สมเหตุสมผลที่จะกำหนดประเภท PersonAge ให้กับ PersonId เพราะทั้งคู่มีประเภทพื้นฐานเหมือนกัน
Voo

16

ฉันคิดว่านี่อาจเป็นคำถาม X / Y ได้เป็นอย่างดี คะแนนเด่นจากคำถาม ...

แรงจูงใจของฉันคือการเพิ่มการใช้ระบบประเภทเพื่อการตรวจสอบความถูกต้องเวลารวบรวม

... และจากความคิดเห็นของคุณอย่างละเอียด:

ฉันไม่ต้องการที่จะทดแทนคนอื่นโดยปริยาย

ขอโทษนะถ้าฉันพลาดบางอย่าง แต่ ... ถ้านี่คือเป้าหมายของคุณทำไมคุณถึงพูดถึงมรดกบนโลก? ความสามารถในการทดแทนโดยนัยคือ ... เหมือน ... ทุกสิ่ง คุณทราบหลักการทดแทน Liskov หรือไม่

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

และสำหรับผู้ที่อาจไม่ชอบคำตอบ X / Y ฉันคิดว่าชื่ออาจยังตอบได้โดยอ้างอิงจาก LSP ชนิดดั้งเดิมดั้งเดิมเพราะพวกเขาทำสิ่งที่ง่ายมากและนั่นคือทั้งหมดที่พวกเขาทำ การปล่อยให้พวกเขาได้รับการสืบทอดและการสร้างผลกระทบที่ไม่มีที่สิ้นสุดของพวกเขาจะนำไปสู่ หากฉันคิดว่าThales Pereiraอาจมองในแง่ดีฉันจะไม่พูดถึงความคิดเห็นที่น่าอัศจรรย์นี้ :

มีปัญหาเพิ่มเติมว่าถ้าใครบางคนสามารถสืบทอดจาก Int คุณจะมีรหัสผู้บริสุทธิ์เช่น "int x = y + 2" (โดยที่ Y คือคลาสที่ได้รับ) ซึ่งตอนนี้เขียนบันทึกไปยังฐานข้อมูลเปิด URL และ เอลวิสคืนชีพอีกครั้ง ประเภทดั้งเดิมควรจะปลอดภัยและมีพฤติกรรมที่กำหนดไว้ชัดเจน

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

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


4

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

มี OOP หลักสองภาษา (ที่ได้รับการจัดการทำงานบน VM) ที่มีคุณสมบัติพื้นฐาน: C # และ Java ภาษาอื่น ๆ อีกมากมายไม่มีภาษาดั้งเดิมในตอนแรกหรือใช้เหตุผลที่คล้ายกันเพื่อให้พวกเขา / ใช้พวกเขา

ดั้งเดิมคือการประนีประนอมเพื่อประสิทธิภาพ สำหรับแต่ละวัตถุคุณต้องการพื้นที่สำหรับส่วนหัวของวัตถุ (ใน Java โดยทั่วไปคือ 2 * 8 ไบต์บน 64- บิต VMs) รวมทั้งเขตข้อมูลของมันรวมถึงการเติมในที่สุด (ในฮอตสปอต 8) ดังนั้นintวัตถุที่เป็นวัตถุจะต้องมีหน่วยความจำอย่างน้อย 24 ไบต์เพื่อเก็บไว้รอบแทนที่จะเป็นเพียง 4 ไบต์ (ใน Java)

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

Stringเป็นอีกกรณีหนึ่ง ทั้งใน Java และ C # Stringเป็นวัตถุ แต่ใน C # มันปิดผนึกและใน Java สุดท้าย นั่นเป็นเพราะทั้งไลบรารีมาตรฐาน Java และ C # ต้องการStrings ให้ไม่เปลี่ยนรูปและการแบ่งคลาสย่อยจะทำให้การเปลี่ยนแปลงไม่ได้นี้

ในกรณีของ Java, VM สามารถ (และไม่) ฝึกงาน Strings และ "พูล" พวกเขาช่วยให้ประสิทธิภาพที่ดีขึ้น ใช้งานได้เฉพาะเมื่อ Strings ไม่เปลี่ยนรูปอย่างแท้จริง

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


1
คำตอบนี้คือ ... แคบ to allow inheritance, one needs runtime type information.เท็จ For every object, some data regarding the type of the object has to be stored.เท็จ There are two mainstream OOP languages that feature primitives: C# and Java.ตอนนี้ C ++ ไม่ใช่กระแสหลักตอนนี้? ฉันจะใช้มันเป็นข้อโต้แย้งของฉันเป็นข้อมูลประเภทรันไทม์ เป็นคำ C ++ มันอย่างไม่จำเป็นเว้นแต่ใช้หรือdynamic_cast typeidและแม้ว่า RTTI จะเปิดอยู่การสืบทอดจะกินเนื้อที่ถ้าคลาสมีvirtualวิธีการที่ตารางของวิธีการต่อคลาสจะต้องชี้ไปตามแต่ละอินสแตนซ์
underscore_d

1
การสืบทอดใน C ++ นั้นทำงานได้แตกต่างกันมากในภาษาที่รันบน VM การจัดส่งเสมือนต้องใช้ RTTI สิ่งที่ไม่ได้เป็นส่วนหนึ่งของ C ++ การสืบทอดโดยไม่มีการแจกจ่ายเสมือนนั้นมี จำกัด มากและฉันไม่แน่ใจด้วยซ้ำว่าคุณควรเปรียบเทียบกับการสืบทอดด้วยการจัดส่งเสมือน นอกจากนี้แนวคิดของ "วัตถุ" นั้นแตกต่างกันมากใน C ++ จากนั้นเป็น C # หรือ Java คุณถูกต้องมีบางสิ่งที่ฉันสามารถพูดได้ดีกว่า แต่ tbh เข้าไปในทุกจุดที่เกี่ยวข้องอย่างรวดเร็วนำไปสู่การต้องเขียนหนังสือเกี่ยวกับการออกแบบภาษา
Polygnome

3
นอกจากนี้ไม่ใช่กรณีที่ "การจัดส่งเสมือนจริงต้องใช้ RTTI" ใน C ++ อีกครั้งเท่านั้นdynamic_castและtypeinfoต้องการสิ่งนั้น การจัดส่งเสมือนจริงถูกนำไปใช้งานจริงโดยใช้ตัวชี้ไปยัง vtable สำหรับคลาสคอนกรีตของวัตถุดังนั้นจึงอนุญาตให้เรียกฟังก์ชันที่ถูกต้องได้ แต่ไม่ต้องการรายละเอียดของประเภทและความสัมพันธ์ที่มีอยู่ใน RTTI คอมไพเลอร์ทั้งหมดจำเป็นต้องรู้ว่าคลาสของวัตถุนั้นเป็น polymorphic หรือไม่และถ้าเป็นเช่นนั้นสิ่งที่ vptr ของอินสแตนซ์คือ -fno-rttiหนึ่งนิดสามารถรวบรวมเรียนส่งจริงด้วย
underscore_d

2
ในความเป็นจริงแล้วเป็นอีกวิธีหนึ่งในการจัดทำ RTTI ต้องการการจัดส่งเสมือนจริง แท้จริง -C ++ ไม่อนุญาตให้ใช้dynamic_castในคลาสที่ไม่มีการจัดส่งเสมือน เหตุผลในการใช้งานคือ RTTI นั้นถูกนำไปใช้โดยทั่วไปในฐานะสมาชิกที่ซ่อนของ vtable
MSalters

1
@MilesRout C ++ มีทุกสิ่งที่ภาษาต้องการสำหรับ OOP อย่างน้อยก็มาตรฐานที่ค่อนข้างใหม่กว่า บางคนอาจแย้งว่ามาตรฐาน C ++ ที่เก่ากว่านั้นขาดบางสิ่งที่จำเป็นสำหรับภาษา OOP แต่ถึงแม้จะยืดออกไป C ++ ไม่ใช่ภาษา OOP ระดับสูงเนื่องจากช่วยให้สามารถควบคุมบางสิ่งได้โดยตรงและระดับต่ำกว่า แต่จะอนุญาตให้ใช้ OOP ได้ (ระดับสูง / ระดับต่ำที่นี่ในแง่ของนามธรรมภาษาอื่น ๆ เช่นคนที่มีการจัดการที่เป็นนามธรรมมากกว่าระบบแล้ว C ++ ดังนั้นสิ่งที่เป็นนามธรรมของพวกเขาจะสูงกว่า)
Polygnome

4

ในภาษา OOP แบบสแตติกที่สำคัญส่วนใหญ่การพิมพ์ย่อยจะถูกมองว่าเป็นวิธีการขยายประเภทและเพื่อแทนที่วิธีปัจจุบันของประเภท

ในการทำเช่นนั้น 'วัตถุ' จะมีตัวชี้ตามชนิดของวัตถุ นี่คือค่าโสหุ้ย: รหัสในวิธีการที่ใช้Shapeอินสแตนซ์แรกมีการเข้าถึงข้อมูลชนิดของอินสแตนซ์นั้นก่อนที่จะรู้Area()วิธีการที่ถูกต้องในการเรียก

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

ดังนั้นคำตอบ:

ทำไมภาษา OOP แบบคงที่ที่สำคัญจึงป้องกันการสืบทอดดั้งเดิม

คือ:

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

อย่างไรก็ตามเราเริ่มได้รับภาษาที่อนุญาตให้ตรวจสอบแบบสแตติกตามคุณสมบัติของตัวแปรอื่น ๆ แล้ว 'พิมพ์' ตัวอย่างเช่น F # มี "มิติ" และ "หน่วย" เพื่อให้คุณไม่สามารถเพิ่มความยาวให้กับพื้นที่ตัวอย่างเช่น .

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


หน่วยวัด # F เป็นคุณลักษณะที่ดีแม้ว่าจะมีชื่อผิด นอกจากนี้ยังรวบรวมเวลาเท่านั้นดังนั้นจึงไม่เป็นประโยชน์อย่างยิ่งเมื่อใช้แพ็คเกจ NuGet ที่คอมไพล์ ทิศทางที่ถูกต้องแม้ว่า
Den

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

3

ฉันไม่แน่ใจว่าฉันมองอะไรบางอย่างที่นี่ แต่คำตอบนั้นง่ายมาก:

  1. คำจำกัดความของดั้งเดิมคือ: ค่าดั้งเดิมไม่ใช่วัตถุชนิดดั้งเดิมไม่ใช่ประเภทวัตถุดั้งเดิมไม่ได้เป็นส่วนหนึ่งของระบบวัตถุ
  2. การสืบทอดเป็นคุณสมบัติของระบบวัตถุ
  3. ก่อนอื่นไม่สามารถมีส่วนร่วมในการสืบทอด

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

ใน C ++ ดั้งเดิมนั้นโดยพื้นฐานแล้วเป็นมรดกที่สืบทอด (pun ตั้งใจ) จาก C ดังนั้นพวกเขาจึงไม่ได้มีส่วนร่วมในระบบวัตถุ (และดังนั้นจึงสืบทอด) เพราะ C ไม่มีทั้งระบบวัตถุและการสืบทอด

ในชวาดั้งเดิมนั้นเป็นผลมาจากความพยายามที่เข้าใจผิดในการปรับปรุงประสิทธิภาพ Primitives ยังเป็นชนิดของค่าเพียงอย่างเดียวในระบบซึ่งในความเป็นจริงเป็นไปไม่ได้ที่จะเขียนชนิดของค่าใน Java และเป็นไปไม่ได้ที่วัตถุจะเป็นชนิดของค่า ดังนั้นนอกเหนือจากความจริงที่ว่าพื้นฐานไม่ได้มีส่วนร่วมในระบบวัตถุและทำให้ความคิดของ "มรดก" ไม่ได้ทำให้ความรู้สึกแม้ถ้าคุณสามารถรับมรดกจากพวกคุณจะไม่สามารถที่จะรักษา " ค่า-Ness" ซึ่งแตกต่างจากเช่นC♯ซึ่งจะมีค่าประเภท ( structs) ซึ่งยังคงเป็นวัตถุ

อีกสิ่งหนึ่งก็คือการไม่สามารถรับมรดกได้นั้นก็ไม่ได้มีลักษณะเฉพาะกับแบบดั้งเดิม ในC♯นั้นstructสืบทอดมาจากSystem.Objectและสามารถใช้interfaces แต่พวกเขาไม่สามารถสืบทอดจากหรือสืบทอดโดยclasses หรือstructs นอกจากนี้sealed classes ไม่สามารถสืบทอดจาก ใน Java final classไม่สามารถรับ es จาก

tl; dr :

ทำไมภาษา OOP แบบคงที่ที่สำคัญจึงป้องกันการสืบทอดดั้งเดิม

  1. ดึกดำบรรพ์ไม่ได้เป็นส่วนหนึ่งของระบบวัตถุ (ตามคำนิยามหากพวกเขาพวกเขาจะไม่ดึกดำบรรพ์) ความคิดของการถ่ายทอดทางพันธุกรรมจะเชื่อมโยงกับระบบวัตถุมรดกสืบทอดดั้งเดิม ergo เป็นความขัดแย้งในแง่
  2. primitives ไม่ซ้ำกันหลายชนิดอื่น ๆ ไม่สามารถสืบทอดเช่นกัน ( finalหรือsealedใน Java หรือC♯, structs ในC♯, case classes ใน Scala)

3
อืม ... ฉันรู้ว่ามันออกเสียงว่า "C Sharp" แต่เอ๊ะ
นาย Lister

ฉันคิดว่าคุณเข้าใจผิดด้าน C ++ มันไม่ใช่ภาษา OO ที่บริสุทธิ์เลย วิธีการเรียนโดยค่าเริ่มต้นไม่ได้virtualซึ่งหมายความว่าพวกเขาไม่เชื่อฟัง LSP เช่นstd::stringไม่ใช่แบบดั้งเดิม แต่มันทำงานได้มากเหมือนค่าอื่น ความหมายของค่าดังกล่าวค่อนข้างทั่วไปส่วน STL ทั้งหมดของ C ++ จะถือว่า
MSalters

2
'ใน Java พื้นฐานมาจากความพยายามที่เข้าใจผิดในการปรับปรุงประสิทธิภาพ' ฉันคิดว่าคุณไม่มีความคิดเกี่ยวกับขนาดของประสิทธิภาพการทำงานของการใช้งานแบบดั้งเดิมเป็นประเภทวัตถุที่ขยายได้ของผู้ใช้ การตัดสินใจในภาษาจาวานั้นมีทั้งเจตนาและตั้งขึ้นอย่างดี แค่คิดว่าต้องจัดสรรหน่วยความจำทุกครั้งที่intคุณใช้ การจัดสรรแต่ละครั้งใช้เวลาในการสั่งของ 100ns รวมทั้งค่าใช้จ่ายในการเก็บขยะ ที่เปรียบเทียบกับวงจรซีพียูเดียวบริโภคโดยเพิ่มสองดั้งเดิมints รหัส java ของคุณจะคลานไปหากผู้ออกแบบภาษาตัดสินใจเป็นอย่างอื่น
cmaster

1
@cmaster: Scala ไม่มีพื้นฐานและประสิทธิภาพของตัวเลขนั้นเหมือนกับของ Java เพราะดีมันรวบรวมจำนวนเต็มลงใน JVM ดั้งเดิมintดังนั้นพวกเขาจึงทำเหมือนกัน (Scala พื้นเมืองรวบรวมพวกเขาเข้าไปลงทะเบียนเครื่องดั้งเดิม Scala.js รวบรวมพวกเขาเข้าไปในดั้งเดิม ECMAScript Numbers.) ทับทิมไม่ได้มีพื้นฐาน แต่ YARV และ Rubinius รวบรวมจำนวนเต็มจำนวนเต็มลงในเครื่องดั้งเดิม JRuby รวบรวมพวกเขาเข้าไปใน JVM ดั้งเดิมlongs สวยมากทุกการใช้งานเสียงกระเพื่อมสมอลล์ทอล์คหรือทับทิมใช้พื้นฐานใน VM นั่นคือที่การเพิ่มประสิทธิภาพการทำงาน ...
Jörg W Mittag

1
…เป็นของ: ในคอมไพเลอร์ไม่ใช่ภาษา
Jörg W Mittag

2

Joshua Bloch ใน“ Effective Java” แนะนำให้ออกแบบอย่างชัดเจนสำหรับการสืบทอดหรือการห้าม คลาสดั้งเดิมไม่ได้ออกแบบมาสำหรับการสืบทอดเนื่องจากถูกออกแบบมาให้ไม่เปลี่ยนรูปและการอนุญาตให้สืบทอดสามารถเปลี่ยนได้ในคลาสย่อยดังนั้นจึงทำลายหลักการ Liskovและมันจะเป็นแหล่งที่มาของข้อบกพร่องมากมาย

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

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


2

ในระดับนามธรรมคุณสามารถรวมสิ่งที่คุณต้องการในภาษาที่คุณออกแบบ

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

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

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

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

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

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


1

โดยทั่วไปแล้ว

หากคลาสเป็นนามธรรม (อุปมา: กล่องที่มีรู) มันก็โอเค (จำเป็นต้องมีบางสิ่งที่ใช้งานได้!) เพื่อ "เติมหลุม" นั่นคือเหตุผลที่เราคลาสคลาสย่อยที่เป็นนามธรรม

ถ้าคลาสเป็นรูปธรรม (อุปมา: เต็มกล่อง) ก็ไม่เป็นไรที่จะแก้ไขสิ่งที่มีอยู่เพราะถ้าเต็มแล้วมันจะเต็ม เราไม่มีที่ว่างสำหรับเพิ่มอะไรเพิ่มเติมในกล่องนั่นเป็นเหตุผลว่าทำไมเราไม่ควรเรียนคลาสซับคลาสคอนกรีต

ด้วยวิธีดั้งเดิม

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



ลิงค์นี้เป็นความคิดเห็นการออกแบบที่น่าสนใจ ต้องการความคิดที่มากขึ้นสำหรับฉัน
Den

1

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

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

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