“ Parent x = new Child ();” แทนที่จะเป็น“ Child x = new Child ();” เป็นการปฏิบัติที่ไม่ดีถ้าเราสามารถใช้อันหลังได้หรือไม่?


32

ตัวอย่างเช่นฉันเคยเห็นรหัสบางอย่างที่สร้างชิ้นส่วนเช่นนี้:

Fragment myFragment=new MyFragment();

ซึ่งประกาศตัวแปรเป็นชิ้นส่วนแทน MyFragment ซึ่ง MyFragment เป็นคลาสย่อยของ Fragment ฉันไม่พอใจรหัสบรรทัดนี้เพราะฉันคิดว่ารหัสนี้ควรเป็น:

MyFragment myFragment=new MyFragment();

ข้อไหนเจาะจงกว่ากันจริงหรือ?

หรือโดยทั่วไปของคำถามเป็นการใช้งานไม่ถูกต้อง:

Parent x=new Child();

แทน

Child x=new Child();

หากเราสามารถเปลี่ยนคนที่เคยเป็นคนหลังได้โดยไม่ผิดพลาด


6
หากเราทำสิ่งหลังไม่มีสิ่งใดที่เป็นนามธรรม / การวางนัยทั่วไปอีกต่อไป แน่นอนว่ามีข้อยกเว้น ...
Walfrat

2
ในโครงการที่ฉันกำลังทำอยู่ฟังก์ชั่นของฉันคาดหวังว่าประเภทผู้ปกครองและเรียกใช้วิธีการประเภทผู้ปกครอง ฉันสามารถยอมรับเด็ก ๆ หลาย ๆ คนและรหัสที่อยู่เบื้องหลังวิธีการ (สืบทอดมาและแทนที่) นั้นแตกต่างกัน แต่ฉันต้องการเรียกใช้รหัสนั้นไม่ว่าเด็กคนใดที่ฉันกำลังใช้อยู่
Stephen S

4
@ Walrat แน่นอนว่ามีจุดเล็ก ๆ น้อย ๆ ในสิ่งที่เป็นนามธรรมเมื่อคุณกำลังสร้างอินสแตนซ์ที่เป็นรูปธรรมอยู่แล้ว แต่คุณยังสามารถคืนค่าประเภทนามธรรม
Jacob Raihle

4
อย่าใช้พ่อแม่ / ลูก ใช้อินเตอร์เฟส / คลาส (สำหรับ Java)
Thorbjørn Ravn Andersen

4
Google "ตั้งโปรแกรมไปยังส่วนต่อประสาน" สำหรับคำอธิบายมากมายว่าทำไมการใช้Parent x = new Child()จึงเป็นความคิดที่ดี
Kevin Workman

คำตอบ:


69

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

ตัวอย่างที่จะมีLinkedListและซึ่งทั้งสองสืบเชื้อสายมาจากArrayList Listหากรหัสจะทำงานได้ดีเท่าเทียมกันกับรายการใด ๆ ก็ไม่มีเหตุผลใดที่จะ จำกัด รหัสนั้นไว้ในคลาสย่อยหนึ่ง ๆ


24
เผง ในการเพิ่มตัวอย่างลองคิดถึงList list = new ArrayList()รหัสที่ใช้ในรายการนั้นไม่สนใจว่าจะเป็นรายการประเภทใดเฉพาะที่ตรงกับสัญญาของส่วนต่อประสานรายการเท่านั้น ในอนาคตเราสามารถเปลี่ยนเป็นการใช้งานรายการอื่นได้อย่างง่ายดายและโค้ดพื้นฐานไม่จำเป็นต้องเปลี่ยนแปลงหรืออัปเดตเลย
อาจจะ __Factor

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

10
@ ไมค์: ฉันหวังว่าจะไปโดยไม่พูดมิฉะนั้นตัวแปรพารามิเตอร์และฟิลด์ทั้งหมดจะถูกประกาศเป็นObject!
JacquesB

3
@JacquesB: เนื่องจากคำถามและคำตอบนี้ได้รับความสนใจอย่างมากฉันคิดว่าการมีความชัดเจนจะเป็นประโยชน์สำหรับคนที่มีระดับความสามารถต่างกัน
Mike

1
มันขึ้นอยู่กับบริบทไม่ใช่คำตอบ
Billal Begueradj

11

คำตอบของ JacquesB นั้นถูกต้อง แต่เป็นนามธรรมเล็กน้อย ให้ฉันเน้นบางแง่มุมให้ชัดเจนยิ่งขึ้น:

ในข้อมูลโค้ดของคุณคุณใช้newคำหลักอย่างชัดเจนและบางทีคุณอาจต้องการดำเนินการขั้นตอนการเริ่มต้นเพิ่มเติมซึ่งมีความเฉพาะเจาะจงสำหรับประเภทคอนกรีต: จากนั้นคุณต้องประกาศตัวแปรด้วยประเภทคอนกรีตเฉพาะนั้น

IFragment fragment = SomeFactory.GiveMeFragments(...);สถานการณ์ที่แตกต่างกันเมื่อคุณได้รับรายการที่มาจากที่อื่นเช่น ที่นี่โรงงานควรกลับมาประเภทนามธรรมที่สุดและรหัสลงไม่ควรขึ้นอยู่กับรายละเอียดการใช้งาน


18
"แม้ว่าจะเป็นนามธรรมเล็กน้อย" Badumtish
rosuav

1
สิ่งนี้ไม่สามารถแก้ไขได้หรือ
beppe9000

6

อาจมีความแตกต่างด้านประสิทธิภาพ

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

ตัวอย่าง: ToStringเป็นการโทรเสมือน แต่Stringถูกปิดผนึก เมื่อวันที่String, ToStringเป็น no-op ดังนั้นหากคุณประกาศเป็นobjectที่โทรเสมือนถ้าคุณประกาศStringคอมไพเลอร์ไม่มีใครรู้มาระดับได้แทนที่วิธีการเพราะชั้นจะปิดผนึกเพื่อให้เป็นไม่มี-op การพิจารณาที่คล้ายกันนำไปใช้กับVSArrayListLinkedList

ดังนั้นหากคุณรู้ประเภทวัตถุที่เป็นรูปธรรม (และไม่มีเหตุผลในการปิดบัง) คุณควรประกาศเป็นประเภทนั้น เมื่อคุณเพิ่งสร้างวัตถุคุณจะรู้ประเภทที่เป็นรูปธรรม


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

และคอมไพเลอร์รู้ว่า "new Child ()" ส่งคืน Child แม้เมื่อกำหนดให้กับผู้ปกครองและตราบใดที่รู้ก็สามารถใช้ความรู้นั้นและเรียกรหัส Child ()
gnasher729

+1 ยังขโมยตัวอย่างของคุณในคำตอบของฉัน = P
Nat

1
โปรดทราบว่ามีการเพิ่มประสิทธิภาพประสิทธิภาพที่ทำให้สิ่งที่สงสัยทั้งหมด ตัวอย่างเช่น HotSpot จะสังเกตเห็นว่าพารามิเตอร์ที่ส่งผ่านไปยังฟังก์ชั่นนั้นเป็นคลาสที่เฉพาะเจาะจงเสมอและปรับให้เหมาะสมตามลำดับ หากข้อสันนิษฐานนั้นกลายเป็นเท็จวิธีการจะถูกยกเลิก แต่สิ่งนี้ขึ้นอยู่กับสภาพแวดล้อมของคอมไพเลอร์ / รันไทม์ ครั้งสุดท้ายที่ผมตรวจสอบ CLR อาจไม่ได้เพิ่มประสิทธิภาพค่อนข้างน่ารำคาญของสังเกตเห็นว่า p จากParent p = new Child()อยู่เสมอเด็ก ..
Voo

4

ความแตกต่างที่สำคัญคือระดับการเข้าถึงที่ต้องการ และทุกคำตอบที่ดีเกี่ยวกับสิ่งที่เป็นนามธรรมเกี่ยวข้องกับสัตว์หรืออย่างนั้นฉันก็บอก

ขอให้เราสมมติว่าคุณมีสัตว์กี่ - และCat Bird Dogตอนนี้สัตว์เหล่านี้มีพฤติกรรมร่วมกันไม่กี่ - move(), และeat() speak()พวกเขากินต่างกันและพูดต่างกัน แต่ถ้าฉันต้องการสัตว์กินหรือพูดฉันไม่สนใจว่าพวกมันจะทำอย่างไร

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

ดังนั้นในรหัสที่ต้องใช้ผู้บุกรุกฉันต้องทำจริงๆ Dog fido = getDog(); fido.barkMenancingly();

แต่ส่วนใหญ่ฉันมีความสุขที่สัตว์มีอุบายที่พวกเขาวูฟ meow หรือ tweet สำหรับปฏิบัติ Animal pet = getAnimal(); pet.speak();

ดังนั้นเพื่อเป็นรูปธรรมมากขึ้น ArrayList มีวิธีการบางอย่างที่Listไม่ ยวดtrimToSize(). ตอนนี้ใน 99% ของกรณีเราไม่สนใจเรื่องนี้ Listเป็นพอเกือบจะไม่ยิ่งใหญ่สำหรับเราในการดูแล แต่มีบางครั้งที่มันเป็น ดังนั้นในบางครั้งเราต้องขอArrayListให้ดำเนินการtrimToSize()และทำให้อาร์เรย์สำรองมีขนาดเล็กลงโดยเฉพาะ

โปรดทราบว่าสิ่งนี้ไม่จำเป็นต้องใช้ในการก่อสร้าง - สามารถทำได้ผ่านวิธีการส่งคืนหรือแม้แต่นักแสดงหากเรามั่นใจจริงๆ


สับสนว่าทำไมสิ่งนี้อาจได้รับการโหวต ดูเหมือนว่าเป็นที่ยอมรับเป็นการส่วนตัว ยกเว้นความคิดของสุนัขยามที่ตั้งโปรแกรมได้ ...
corsiKa

ทำไมฉันต้องอ่านหน้าเพื่อให้ได้คำตอบที่ดีที่สุด? การให้คะแนน
Basilevs

ขอบคุณสำหรับคำที่ใจดี มีข้อดีและข้อเสียของระบบการให้คะแนนและหนึ่งในนั้นคือคำตอบในภายหลังบางครั้งอาจตกค้างอยู่ในฝุ่น มันเกิดขึ้น!
corsiKa

2

โดยทั่วไปคุณควรใช้

var x = new Child(); // C#

หรือ

auto x = new Child(); // C++

สำหรับตัวแปรในตัวเครื่องเว้นแต่จะมีเหตุผลที่ดีที่ตัวแปรจะมีประเภทที่แตกต่างกันเหมือนกับสิ่งที่มันถูกกำหนดค่าเริ่มต้นด้วย

(ที่นี่ฉันเพิกเฉยต่อความจริงที่ว่ารหัส C ++ น่าจะใช้ตัวชี้สมาร์ทมากที่สุดมีหลายกรณีที่ไม่ได้และไม่มีมากกว่าหนึ่งบรรทัดที่ฉันไม่รู้จริง ๆ )

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


1
ใน C ++ ส่วนใหญ่จะสร้างตัวแปรโดยไม่มีคำหลักใหม่: stackoverflow.com/questions/6500313/…
Marian Spanik

1
คำถามนั้นไม่เกี่ยวกับ C # / C ++ แต่เกี่ยวกับปัญหาเชิงวัตถุทั่วไปในภาษาที่มีการพิมพ์แบบสแตติกบางรูปแบบ (เช่นจะเป็นเรื่องไร้สาระที่จะถามในรูปแบบของ Ruby) นอกจากนี้แม้ในสองภาษานี้ฉันไม่เห็นเหตุผลที่ดีว่าทำไมเราควรทำอย่างที่คุณพูด
AnoE

1

ดีเพราะเข้าใจง่ายและแก้ไขรหัสได้ง่าย:

var x = new Child(); 
x.DoSomething();

ดีเพราะมันสื่อถึงเจตนา:

Parent x = new Child(); 
x.DoSomething(); 

ตกลงเพราะเป็นเรื่องธรรมดาและเข้าใจง่าย:

Child x = new Child(); 
x.DoSomething(); 

ตัวเลือกหนึ่งที่ไม่ดีอย่างแท้จริงคือการใช้ParentหากChildมีวิธีการDoSomething()เท่านั้น มันแย่เพราะมันสื่อถึงเจตนาที่ไม่ถูกต้อง:

Parent x = new Child(); 
(x as Child).DoSomething(); // DON'T DO THIS! IF YOU WANT x AS CHILD, STORE x AS CHILD

ตอนนี้เรามาดูเพิ่มเติมเกี่ยวกับกรณีที่คำถามถามถึง:

Parent x = new Child(); 
x.DoSomething(); 

ลองจัดรูปแบบสิ่งนี้แตกต่างกันโดยการเรียกใช้ฟังก์ชันครึ่งหลัง:

WhichType x = new Child(); 
FunctionCall(x);

void FunctionCall(WhichType x)
    x.DoSomething(); 

สิ่งนี้สามารถย่อให้เป็น:

FunctionCall(new Child());

void FunctionCall(WhichType x)
    x.DoSomething();

ที่นี่ฉันคิดว่ามันเป็นที่ยอมรับกันอย่างกว้างขวางว่าWhichTypeควรเป็นประเภทพื้นฐาน / นามธรรมที่สุดที่อนุญาตให้ฟังก์ชั่นทำงาน (เว้นแต่มีข้อกังวลเกี่ยวกับประสิทธิภาพ) ในกรณีนี้ประเภทที่เหมาะสมจะเป็นอย่างใดอย่างหนึ่งParentหรือบางสิ่งบางอย่างParentมาจาก

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


1

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


ในขอบเขตท้องถิ่น

Parent obj = new Child();  // Works
Child  obj = new Child();  // Better
var    obj = new Child();  // Best

โดยหลักการแล้วมันเกี่ยวกับการรักษาข้อมูลประเภทที่เป็นไปได้มากที่สุด หากเราปรับลดรุ่นParentเป็นหลักเราเพียงแค่ดึงข้อมูลประเภทที่อาจเป็นประโยชน์ออกมา

การเก็บรักษาข้อมูลประเภทสมบูรณ์มีข้อดีสี่ประการ:

  1. ให้ข้อมูลเพิ่มเติมกับคอมไพเลอร์
  2. ให้ข้อมูลเพิ่มเติมแก่ผู้อ่าน
  3. รหัสมาตรฐานที่สะอาดกว่า
  4. ทำให้ตรรกะของโปรแกรมไม่แน่นอนมากขึ้น

ข้อได้เปรียบ 1: ข้อมูลเพิ่มเติมเกี่ยวกับคอมไพเลอร์

ประเภทที่ชัดเจนจะใช้ในการแก้ปัญหาวิธีการมากเกินไปและในการเพิ่มประสิทธิภาพ

ตัวอย่าง: การแก้ไขวิธีโอเวอร์โหลด

main()
{
    Parent parent = new Child();
    foo(parent);

    Child  child  = new Child();
    foo(child);
}
foo(Parent arg) { /* ... */ }  // More general
foo(Child  arg) { /* ... */ }  // Case-specific optimizations

ในตัวอย่างข้างต้นการfoo()โทรทั้งสองทำงานแต่ในกรณีหนึ่งเราจะได้รับการแก้ไขวิธีโอเวอร์โหลดที่ดีกว่า

ตัวอย่าง: การปรับให้เหมาะสมของคอมไพเลอร์

main()
{
    Parent parent = new Child();
    var x = parent.Foo();

    Child  child  = new Child();
    var y = child .Foo();
}
class Parent
{
    virtual         int Foo() { return 1; }
}
class Child : Parent
{
    sealed override int Foo() { return 2; }
}

ในตัวอย่างข้างต้นทั้ง.Foo()สายในที่สุดเรียกเดียวกันวิธีการที่ผลตอบแทนoverride 2ในกรณีแรกมีการค้นหาวิธีการเสมือนเพื่อค้นหาวิธีการที่ถูกต้อง การค้นหาวิธีเสมือนนี้ไม่จำเป็นในกรณีที่สองนับตั้งแต่วิธีการsealedนั้น

เครดิต @ Ben ที่ให้เป็นตัวอย่างที่คล้ายกันในคำตอบของเขา

ข้อได้เปรียบ 2: ข้อมูลเพิ่มเติมเพื่อผู้อ่าน

การรู้ประเภทที่แน่นอนคือChildให้ข้อมูลเพิ่มเติมแก่ผู้ที่กำลังอ่านโค้ดทำให้ง่ายต่อการดูว่าโปรแกรมกำลังทำอะไรอยู่

แน่นอนว่าอาจไม่สำคัญกับรหัสที่แท้จริงเนื่องจากทั้งคู่parent.Foo();และchild.Foo();สมเหตุสมผล แต่สำหรับบางคนที่เห็นรหัสเป็นครั้งแรกข้อมูลเพิ่มเติมก็มีประโยชน์เพียงเล็กน้อยเท่านั้น

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

ข้อได้เปรียบ 3: โค้ดที่สะอาดและเป็นมาตรฐานมากขึ้น

ส่วนใหญ่ตัวอย่างรหัส C # ที่ผมเคยเห็นเมื่อเร็ว ๆ นี้ใช้ที่เป็นพื้นชวเลขvarChild

Parent obj = new Child();  // Sub-optimal
Child  obj = new Child();  // Optimal, but anti-pattern syntax
var    obj = new Child();  // Optimal, clean, patterned syntax "everyone" uses now

การดูvarคำสั่งที่ไม่ใช่การประกาศก็ดูออกไป หากมีเหตุผลสถานการณ์สำหรับมันเจ๋ง แต่อย่างอื่นมันดูต่อต้านรูปแบบ

// Clean:
var foo1 = new Person();
var foo2 = new Job();
var foo3 = new Residence();

// Staggered:
Person foo1 = new Person();
Job foo2 = new Job();
Residence foo3 = new Residence();   

ข้อได้เปรียบ 4: ตรรกะโปรแกรมที่ไม่แน่นอนเพิ่มเติมสำหรับการสร้างต้นแบบ

ข้อดีสามประการแรกคือขนาดใหญ่ สถานการณ์ยิ่งไกลของคนนี้

ยังสำหรับคนที่ใช้รหัสเหมือนคนอื่น ๆ ใช้ Excel เรากำลังเปลี่ยนรหัสของเราอยู่ตลอดเวลา บางทีเราไม่จำเป็นต้องเรียกวิธีการที่ไม่ซ้ำกันChildในรหัสรุ่นนี้แต่เราอาจปรับเปลี่ยนหรือปรับเปลี่ยนรหัสในภายหลัง

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

สรุป

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

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


สำคัญ : คำตอบนี้เกี่ยวParentกับกับChildในขอบเขตของวิธีการ ปัญหาของParentvs.Childแตกต่างกันมากสำหรับประเภทส่งคืนข้อโต้แย้งและฟิลด์คลาส


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

"tl; dr การใช้ Child over Parent นั้นดีกว่าในขอบเขตของระบบซึ่งไม่เพียง แต่ช่วยให้สามารถอ่านได้" - ไม่จำเป็นต้อง ... ถ้าคุณไม่ใช้ข้อมูลเฉพาะของเด็กจากนั้นมันจะเพิ่มรายละเอียดมากกว่าที่จำเป็น "แต่ก็จำเป็นที่จะต้องตรวจสอบให้แน่ใจว่าการแก้ไขวิธีการทำงานมากเกินไปทำงานได้อย่างถูกต้องและช่วยให้สามารถรวบรวมได้อย่างมีประสิทธิภาพ" - ขึ้นอยู่กับภาษาการเขียนโปรแกรมเป็นอย่างมาก มีมากมายที่ไม่มีปัญหาใด ๆ ที่เกิดขึ้นจากเรื่องนี้
AnoE

1
คุณมีแหล่งที่มาParent p = new Child(); p.doSomething();และChild c = new Child; c.doSomething();มีพฤติกรรมที่แตกต่างกันอย่างไร อาจเป็นเรื่องแปลกในบางภาษา แต่มันควรจะเป็นที่วัตถุควบคุมพฤติกรรมไม่ใช่การอ้างอิง นั่นคือความงามของมรดก! ตราบใดที่คุณสามารถไว้วางใจการใช้งานลูกเพื่อทำตามสัญญาของการดำเนินการกับผู้ปกครองคุณจะดี
corsiKa

@corsiKa ใช่มันเป็นไปได้ในไม่กี่วิธี สองตัวอย่างรวดเร็วจะเป็นที่วิธีลายเซ็นจะถูกเขียนทับเช่นกับnewคำหลักใน C #และเมื่อมีคำจำกัดความหลายกำลังเช่นกับมรดกหลายใน C
แน็

@corsiKa เพียงสามตัวอย่างรวดเร็ว (เพราะผมคิดว่ามันเป็นความแตกต่างระหว่างเรียบร้อย C ++ และ C #), C # มีปัญหาเช่น C ++ 's ที่คุณยังสามารถได้รับพฤติกรรมนี้มาจากวิธีการที่ชัดเจนอินเตอร์เฟซ มันตลกเพราะภาษาอย่าง C # ใช้อินเตอร์เฟสแทนการสืบทอดหลายส่วนเพื่อหลีกเลี่ยงปัญหาเหล่านี้ แต่การใช้อินเตอร์เฟสก็ยังคงเป็นส่วนหนึ่งของปัญหา - แต่ก็ไม่ได้เลวร้ายนักเพราะในสถานการณ์นั้นมันใช้งานได้เท่านั้น เมื่อเป็นParent interface
Nat

0

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


0

ฉันแนะนำให้ใช้

Child x = new Child();

แทน

Parent x = new Child();

หลังสูญเสียข้อมูลในขณะที่อดีตไม่ได้

คุณสามารถทำทุกอย่างกับอดีตที่คุณสามารถทำกับหลัง แต่ไม่กลับกัน


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

Base-> DerivedL1->DerivedL2

และคุณต้องการเริ่มต้นผลลัพธ์ของnew DerivedL2()การเป็นตัวแปร ใช้พิมพ์ฐานใบคุณเลือกในการใช้หรือBaseDerivedL1

คุณสามารถใช้ได้

Base x = new DerivedL2();

หรือ

DerivedL1 x = new DerivedL2();

ฉันไม่เห็นวิธีที่มีเหตุผลใด ๆ ที่จะชอบอีกทางเลือกหนึ่ง

ถ้าคุณใช้

DerivedL2 x = new DerivedL2();

ไม่มีอะไรจะโต้แย้ง


3
ความสามารถในการทำน้อยถ้าคุณไม่จำเป็นต้องทำมากขึ้นเป็นสิ่งที่ดีใช่ไหม?
ปีเตอร์

1
@ ปีเตอร์ความสามารถในการทำน้อยไม่ถูก จำกัด มันเป็นไปได้เสมอ หากความสามารถในการทำมากขึ้นถูก จำกัด ก็อาจเป็นจุดเจ็บ
R Sahu

แต่การสูญเสียข้อมูลบางครั้งก็เป็นสิ่งที่ดี เกี่ยวกับลำดับชั้นของคุณคนส่วนใหญ่อาจลงคะแนนให้Base(สมมติว่ามันใช้งานได้) คุณชอบมากที่สุด AFAIK ส่วนใหญ่ชอบน้อยที่สุด ฉันจะบอกว่าสำหรับตัวแปรท้องถิ่นมันไม่ค่อยสำคัญ
maaartinus

@maaartinus ฉันประหลาดใจที่ผู้ใช้ส่วนใหญ่ต้องการสูญเสียข้อมูล ฉันไม่เห็นประโยชน์ของการสูญเสียข้อมูลอย่างชัดเจนเหมือนคนอื่น ๆ
R Sahu

เมื่อคุณประกาศBase x = new DerivedL2();คุณจะถูก จำกัด มากที่สุดและสิ่งนี้จะช่วยให้คุณสามารถสลับลูก (Grand-) ลูกด้วยความพยายามน้อยที่สุด ยิ่งกว่านั้นคุณจะเห็นได้ทันทีว่าเป็นไปได้ +++ เราเห็นด้วยว่าvoid f(Base)ดีกว่าvoid f(DerivedL2)ไหม?
maaartinus

0

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

เช่น

<K, V> LinkedHashMap<K, V> keyValuePairsToMap(List<K> keys, List<V> values) {
   //...
}

พารามิเตอร์คือList<T>ประเภททั่วไปมาก ArrayList<T>, LinkedList<T>และอื่น ๆ ทั้งหมดจะได้รับการยอมรับโดยวิธีนี้

ที่สำคัญชนิดกลับเป็นไม่ได้LinkedHashMap<K, V> Map<K, V>หากใครบางคนต้องการกำหนดผลลัพธ์ของวิธีนี้ให้กับ a Map<K, V>พวกเขาสามารถทำได้ มันสื่อสารอย่างชัดเจนว่าผลลัพธ์นี้เป็นมากกว่าแผนที่ แต่เป็นแผนที่ที่มีการสั่งซื้อที่กำหนดไว้

Map<K, V>สมมติว่าวิธีการนี้กลับมา หากผู้โทรต้องการ a LinkedHashMap<K, V>พวกเขาจะต้องทำการตรวจสอบประเภทการส่งและการจัดการข้อผิดพลาดซึ่งยุ่งยากมาก


ตรรกะเดียวกันที่บังคับให้คุณยอมรับชนิดแบบกว้างเป็นพารามิเตอร์ควรใช้กับชนิดส่งคืนเช่นกัน หากเป้าหมายของฟังก์ชั่นคือการจับคู่กุญแจกับค่ามันควรจะคืนค่าMapไม่ใช่LinkedHashMap- คุณต้องการคืนค่า a LinkedHashMapสำหรับฟังก์ชั่นที่ต้องการkeyValuePairsToFifoMapหรือบางอย่างเท่านั้น กว้างกว่าดีกว่าจนกว่าคุณจะมีเหตุผลเฉพาะที่ไม่ควรทำ
corsiKa

@corsiKa อืมฉันยอมรับว่าเป็นชื่อที่ดีกว่า แต่นี่เป็นเพียงตัวอย่างเดียว ฉันไม่เห็นด้วยที่ (โดยทั่วไป) การขยายประเภทผลตอบแทนของคุณนั้นดีกว่า การลบข้อมูลประเภทของคุณโดยไม่ต้องมีเหตุผลใด ๆ หากคุณนึกภาพผู้ใช้ของคุณอย่างสมเหตุสมผลแล้วจะต้องลดประเภททั่วไปที่คุณให้มาพวกเขาแสดงว่าคุณได้ก่อความเสียหาย
Alexander - Reinstate Monica

"ไม่มีเหตุผลใด ๆ " - แต่มีเหตุผล! ถ้าฉันสามารถหลีกหนีจากการคืนรถประเภทที่กว้างกว่านี้ได้มันจะทำให้การปรับสภาพถนนง่ายขึ้น หากมีคนต้องการประเภทที่เฉพาะเจาะจงเนื่องจากพฤติกรรมบางอย่างในนั้นฉันทุกคนกลับประเภทที่เหมาะสม แต่ฉันไม่ต้องการถูกล็อคเป็นประเภทเฉพาะถ้าฉันไม่ได้ให้ฉัน ฉันต้องการที่จะเป็นอิสระในการเปลี่ยนแปลงรายละเอียดของวิธีการของฉันโดยไม่ทำร้ายผู้ที่กินมันและถ้าผมจะเปลี่ยนมันจากการพูดLinkedHashMapจะTreeMapด้วยเหตุผลใดก็ตามตอนนี้ฉันมีจำนวนมากของสิ่งที่จะเปลี่ยนแปลง
corsiKa

ที่จริงแล้วฉันเห็นด้วยกับสิ่งนั้นจนถึงประโยคสุดท้าย คุณเปลี่ยนจากLinkedHashMapการTreeMapไม่ได้มีการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ เช่นการเปลี่ยนจากการArrayList LinkedListมันเป็นการเปลี่ยนแปลงที่มีความสำคัญทางความหมาย ในกรณีนั้นคุณต้องการให้คอมไพเลอร์โยนข้อผิดพลาดเพื่อบังคับให้ผู้บริโภครับค่าส่งคืนเพื่อสังเกตการเปลี่ยนแปลงและดำเนินการที่เหมาะสม
Alexander - Reinstate Monica

แต่มันอาจเป็นการเปลี่ยนแปลงเล็กน้อยหากสิ่งที่คุณใส่ใจคือพฤติกรรมแผนที่ (ซึ่งถ้าคุณทำได้คุณควรทำ) หากสัญญาคือ "คุณจะได้รับแผนที่ค่าคีย์" และลำดับไม่สำคัญ (ซึ่งเป็นจริงสำหรับ แผนที่ส่วนใหญ่) จากนั้นไม่สำคัญว่าจะเป็นแผนผังต้นไม้หรือแฮช ตัวอย่างเช่นThread#getAllStackTraces()- ส่งคืน a Map<Thread,StackTraceElement[]>แทนที่จะเป็นแบบHashMapเฉพาะเพื่อไปตามถนนที่พวกเขาสามารถเปลี่ยนเป็นประเภทใดก็ได้ที่Mapพวกเขาต้องการ
corsiKa
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.