วิธีจัดการกับวิธีการที่ถูกเพิ่มเข้ามาสำหรับชนิดย่อยในบริบทของความหลากหลาย?


14

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

คุณมีคอลเลคชั่นสัตว์และเรียกร้องให้สัตว์ทุกตัวทำงานeatและคุณไม่สนใจว่ามันจะกินสุนัขหรือแมว แต่ในลำดับชั้นเดียวกับที่คุณมีสัตว์ที่มีเพิ่มเติม - อื่น ๆ กว่าสืบทอดและดำเนินการจากชั้นเรียนAnimalเช่นmakeEggs, getBackFromTheFreezedStateและอื่น ๆ ดังนั้นในบางกรณีในการทำงานของคุณคุณอาจต้องการทราบประเภทเฉพาะเพื่อเรียกใช้พฤติกรรมเพิ่มเติม

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


7
ชนิดของความหลากหลายที่คุณอธิบายไว้เรียกว่าความหลากหลายหลายรูปแบบแต่มันไม่ได้เป็นชนิดเดียว (ดูความแตกต่าง ) คุณไม่ต้องสร้างลำดับชั้นของคลาสเพื่อทำ polymorphism (และจริง ๆ แล้วฉันยืนยันว่าการสืบทอดไม่ใช่วิธีที่ใช้กันทั่วไปที่สุดในการทำให้เกิดความหลากหลายของ subtyping การใช้ส่วนต่อประสานนั้นแพร่หลายมากขึ้น)
Vincent Savard

24
หากคุณกำหนดEaterอินเทอร์เฟซด้วยeat()วิธีการในฐานะลูกค้าคุณไม่สนใจว่าการHumanติดตั้งจะต้องมีการโทรครั้งแรกwashHands()และgetDressed()เป็นรายละเอียดการใช้งานของคลาสนี้ หากในฐานะลูกค้าคุณให้ความสำคัญกับความจริงข้อนี้คุณอาจไม่ได้ใช้เครื่องมือที่ถูกต้องสำหรับงาน
Vincent Savard

3
คุณต้องพิจารณาด้วยว่าในตอนเช้ามนุษย์อาจต้องกินอาหารgetDressedก่อนeatไม่ใช่อาหารกลางวัน ขึ้นอยู่กับสถานการณ์ของคุณwashHands();if !dressed then getDressed();[code to actually eat]อาจเป็นวิธีที่ดีที่สุดในการดำเนินการนี้สำหรับมนุษย์ ความเป็นไปได้อีกอย่างคือจะเกิดอะไรขึ้นถ้าสิ่งอื่น ๆ ต้องการwashHandsและ / หรือgetDressedถูกเรียก? สมมติว่าคุณมีleaveForWork? คุณอาจจำเป็นต้องจัดโครงสร้างโฟลว์โปรแกรมของคุณเพื่อให้มันเรียกใช้มานานแล้ว
Duncan X Simpson

1
โปรดทราบว่าการตรวจสอบกับชนิดที่แน่นอนอาจเป็นกลิ่นรหัสใน OOP แต่เป็นวิธีปฏิบัติทั่วไปใน FP (เช่นใช้การจับคู่รูปแบบเพื่อกำหนดประเภทของสหภาพที่มีการแบ่งแยกและดำเนินการกับมัน)
Theodoros Chatzigiannakis

3
ระวังตัวอย่างห้องโรงเรียนของลำดับชั้น OO เช่นสัตว์ โปรแกรมจริงแทบไม่เคยมี taxonomies ที่สะอาดเช่นนี้ เช่นericlippert.com/2015/04/27/wizards-and-warriors-part-one หรือถ้าคุณต้องการไปทั้งหมูและคำถามกระบวนทัศน์ทั้งหมด: การเขียนโปรแกรมเชิงวัตถุไม่ดี
jpmc26

คำตอบ:


18

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

ตัวอย่างเช่นคุณพูดในตอนเช้าสัตว์ต่าง ๆ ทำสิ่งต่าง ๆ วิธีการเกี่ยวกับคุณแนะนำวิธีการgetUp()หรือprepareForDay()หรือสิ่งที่ต้องการ จากนั้นคุณสามารถดำเนินการต่อด้วย polymorphism และให้สัตว์แต่ละตัวดำเนินการตามปกติในตอนเช้า

หากคุณต้องการแยกความแตกต่างระหว่างสัตว์คุณไม่ควรเก็บมันไว้ในรายการโดยไม่เลือกหน้า

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


33

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

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

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

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

Vehicle
Road
Signal

เราสามารถก้าวไปข้างหน้าด้วยสิ่งต่าง ๆ และคนเดินรถไฟ แต่เราจะทำให้มันง่าย

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

interface Vehicle {
  move(Road road);
  navigate(Road... intersection);
}

นี่อาจจะง่ายเกินไป แต่เป็นการเริ่มต้น ตอนนี้ แล้วสิ่งอื่นใดที่ยานพาหนะอาจทำ พวกเขาสามารถปิดถนนและกลายเป็นคู เป็นส่วนหนึ่งของการจำลองหรือไม่? ไม่ไม่ต้องการ รถยนต์และรถโดยสารบางคันมีระบบไฮดรอลิกส์ที่ทำให้พวกเขาเด้งหรือคุกเข่า เป็นส่วนหนึ่งของการจำลองหรือไม่? ไม่ไม่ต้องการ รถยนต์ส่วนใหญ่เผาไหม้น้ำมันเบนซิน บางคนทำไม่ได้ โรงไฟฟ้าเป็นส่วนหนึ่งของการจำลองหรือไม่? ไม่ไม่ต้องการ ขนาดล้อ? ไม่ต้องการมัน ระบบนำทาง GPS ระบบ Infotainment ไม่ต้องการ em

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

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


13
นี่เป็นคำตอบที่ดี "ด้วยเหตุนี้ฉันคิดว่าการสร้างส่วนต่อประสาน OO จากรหัสที่โต้ตอบกับพวกเขามักจะดีกว่า" อย่างแน่นอนและฉันเถียงมันเป็นวิธีเดียว คุณไม่สามารถรู้สัญญาสาธารณะของอินเทอร์เฟซได้เพียงแค่ผ่านการใช้งานมันถูกกำหนดเสมอจากมุมมองของลูกค้า (และเป็นหมายเหตุด้านนี่คือสิ่งที่เป็นจริงเกี่ยวกับ TDD)
Vincent Savard

@VincentSavard "ฉันเถียงว่าเป็นวิธีเดียวเท่านั้น" คุณพูดถูก ฉันเดาว่าเหตุผลที่ฉันไม่ได้ทำอย่างนั้นก็คือเมื่อคุณมีความคิดคุณก็จะสามารถอินเทอร์เฟซออกมาจากนั้นปรับแต่งวิธีนี้ ในที่สุดเมื่อคุณลงไปที่ทองเหลือง tacks มันเป็นสิ่งเดียวที่มีความสำคัญ
JimmyJames

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

9

TL; DR:

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

ก่อนอื่นลองอยู่กับeat()ตัวอย่างของคุณ

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

กลับไปที่ซอฟต์แวร์:

ในฐานะที่เป็นHumanตัวอย่างจะไม่กินโดยไม่ต้องปัจจัยพื้นฐานผมมีHuman's eat()วิธีการทำwashHands()และgetDressed()ถ้าเกิดว่ายังไม่ได้รับการดำเนินการ มันไม่ควรเป็นงานของคุณในฐานะeat()ผู้โทรที่จะรู้เกี่ยวกับลักษณะเฉพาะนั้น ทางเลือกที่ดื้อรั้นของมนุษย์คือการยกเว้น ("ฉันไม่ได้เตรียมที่จะกิน!") ถ้าเงื่อนไขไม่พบทำให้คุณผิดหวัง แต่อย่างน้อยก็ทราบว่าการกินไม่ได้ผล

เกี่ยวกับmakeEggs()อะไร

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


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

อะไรคำตอบนี้อธิบายมักจะเรียกว่าLiskov ชดเชยหลักการ
ฟิลิปป์

2

คำตอบนั้นง่ายมาก

วิธีจัดการกับวัตถุที่สามารถทำได้มากกว่าที่คุณคาดไว้

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

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

ตัวอย่างเช่นใน pseudocode:

interface IEater { void Eat(); }
interface IMorningRoutinePerformer { void DoMorningRoutine(); }
interface IAnimal : IEater, IMorningPerformer;
interface IHuman : IEater, IMorningPerformer; 
{
  void WashHands();
  void GetDressed();
}

void MorningTime()
{
   IList<IMorningRoutinePerformer> items = Service.GetMorningPerformers();
   foreach(item in items) { item.DoMorningRoutine(); }
}

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

ความแตกต่างตาย

หรือไม่

คุณต้องค้นหาประเภทของวัตถุ

ทำไมสมมติว่า? ฉันคิดว่านี่อาจเป็นข้อสันนิษฐานที่ผิด

มีวิธีการทั่วไปในการจัดการกรณีนี้หรือไม่?

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

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

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

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


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