“ ถ้าวิธีการถูกนำมาใช้ใหม่โดยไม่มีการเปลี่ยนแปลงให้ใส่วิธีการในคลาสพื้นฐานมิฉะนั้นสร้างอินเทอร์เฟซ” เป็นกฎที่ดี?


10

เพื่อนร่วมงานของฉันเกิดขึ้นกับกฎง่ายๆสำหรับการเลือกระหว่างการสร้างชั้นฐานหรืออินเตอร์เฟซ

เขาพูดว่า:

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

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

พิจารณาการเรียนcatและdogซึ่งขยายชั้นเรียนและมีวิธีการเดียวmammal pet()จากนั้นเราจะเพิ่มชั้นเรียนซึ่งไม่ขยายอะไรและมีวิธีการเดียวalligatorslither()

ตอนนี้เราต้องการเพิ่มeat()วิธีการทั้งหมดของพวกเขา

หากการดำเนินการตามeat()วิธีการที่จะตรงเดียวกันcat, dogและalligatorเราควรสร้างชั้นฐาน (สมมุติว่าanimal) ซึ่งใช้วิธีการนี้

อย่างไรก็ตามหากการติดตั้งใช้งานalligatorแตกต่างกันเล็กน้อยเราควรสร้างIEatส่วนต่อประสานและสร้างmammalและalligatorนำไปใช้งาน

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

การทำตามกฎของหัวแม่มือนี้มีค่าหรือไม่


27
alligatorแน่นอนว่าการติดตั้งใช้งานeatแตกต่างกันตามที่ยอมรับcatและdogเป็นพารามิเตอร์
sbichenko

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

2
เมื่อคุณวาดภาพตัวเองในมุมที่ดีที่สุดที่จะอยู่ในประตูที่ใกล้ที่สุด
JeffO

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

2
Proff CS ของฉันสอนว่าควรจะ superclasses is aและอินเตอร์เฟซหรือacts like isดังนั้นis aสัตว์เลี้ยงลูกด้วยนมและสุนัขacts likeกิน นี่จะบอกเราว่าสัตว์เลี้ยงลูกด้วยนมควรเป็นคลาสและผู้กินควรเป็นส่วนต่อประสาน มันเป็นคำแนะนำที่เป็นประโยชน์เสมอ Sidenote: ตัวอย่างของการisจะเป็นหรือThe cake is eatable The book is writable
MirroredFate

คำตอบ:


13

ฉันไม่คิดว่านี่เป็นกฎง่ายๆ หากคุณกังวลเกี่ยวกับการนำโค้ดกลับมาใช้ใหม่คุณสามารถใช้PetEatingBehaviorบทบาทที่จะกำหนดฟังก์ชั่นการกินสำหรับแมวและสุนัข จากนั้นคุณสามารถมีIEatและนำรหัสมาใช้ร่วมกัน

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

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

ในการอ้างถึง Rich Hickey: Simple! = Easy


คุณสามารถให้ตัวอย่างพื้นฐานของPetEatingBehaviorการใช้งานได้หรือไม่
sbichenko

1
@ exizt อันดับแรกฉันไม่ดีที่เลือกชื่อ ดังนั้นPetEatingBehaviorอาจเป็นสิ่งที่ผิด ฉันไม่สามารถให้คุณใช้งานได้เนื่องจากนี่เป็นเพียงตัวอย่างของเล่น แต่ฉันสามารถอธิบายขั้นตอนการเปลี่ยนโครงสร้างใหม่ได้: มันมีนวกรรมิกซึ่งใช้ข้อมูลทั้งหมดที่แมว / สุนัขใช้ในการกำหนดeatวิธีการ (ตัวอย่างฟันฟันกระเพาะอาหาร ฯลฯ ) มันมีรหัสทั่วไปสำหรับแมวและสุนัขที่ใช้ในeatวิธีการของพวกเขา คุณเพียงแค่ต้องสร้างPetEatingBehaviorสมาชิกและส่งต่อสายไปที่ dog.eat/cat.eat
Simon Bergot

ฉันไม่อยากเชื่อเลยว่านี่เป็นคำตอบเดียวของหลาย ๆ คนที่จะ
แย่งชิง

1
@DannyTuppeny ทำไมไม่?
sbichenko

4
@ exizt การสืบทอดจากชั้นเรียนเป็นเรื่องใหญ่ คุณกำลังพึ่งพาบางสิ่งบางอย่าง (ซึ่งมีแนวโน้มถาวร) ซึ่งในหลายภาษาคุณสามารถมีได้เพียงหนึ่งในนั้น การใช้รหัสซ้ำเป็นสิ่งที่ไม่สำคัญพอสมควรที่จะใช้คลาสฐานนี้บ่อยๆเท่านั้น จะเกิดอะไรขึ้นถ้าคุณจบลงด้วยวิธีการที่คุณต้องการแบ่งปันให้กับคลาสอื่น แต่มันมีคลาสพื้นฐานอยู่แล้วเนื่องจากการใช้วิธีการอื่น ๆ (หรือแม้กระทั่งว่าเป็นส่วนหนึ่งของลำดับชั้น "ของจริง" ที่ใช้ polymorphically (?)) ใช้การสืบทอดเมื่อ ClassA เป็นประเภทของ ClassB (เช่นรถยนต์สืบทอดจากยานพาหนะ) ไม่ใช่เมื่อแชร์รายละเอียดการใช้งานภายใน :-)
Danny Tuppeny

11

การทำตามกฎของหัวแม่มือนี้มีค่าหรือไม่

เป็นกฎง่ายๆ แต่ฉันรู้ว่าสถานที่ที่ฉันละเมิดมัน

เพื่อความเป็นธรรมฉันใช้คลาสพื้นฐาน (นามธรรม) มากกว่าเพื่อนอื่น ๆ ฉันทำสิ่งนี้เป็นการตั้งโปรแกรมป้องกัน

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

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


1
เมื่ออ่านคำตอบนี้อีก 3 ปีต่อมาฉันพบว่านี่เป็นจุดที่ยอดเยี่ยม: โดยการเลือกใช้คลาสพื้นฐานแทนที่จะเป็นส่วนต่อประสานคุณกำลังพูดว่า "นี่คือฟังก์ชั่นหลักของชั้นเรียน (และผู้รับมรดก) ไม่เหมาะสมในการผสมจำใจกับฟังก์ชั่นอื่น ๆ "
sbichenko

9

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

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

นี่คือวิธีที่ศาสตราจารย์ของฉันสอนความแตกต่าง:

superclasses ควรจะเป็นis aและอินเตอร์เฟซหรือacts like isดังนั้นis aสัตว์เลี้ยงลูกด้วยนมและสุนัขacts likeกิน นี่จะบอกเราว่าสัตว์เลี้ยงลูกด้วยนมควรเป็นคลาสและผู้กินควรเป็นส่วนต่อประสาน ตัวอย่างของisจะเป็นThe cake is eatableหรือThe book is writable(ทำให้ทั้งสองeatableและwritableอินเทอร์เฟซ)

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

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

แค่สองเซ็นต์ของฉัน


6

ตามคำร้องขอของ exizt ฉันกำลังขยายความคิดเห็นของฉันไปยังคำตอบที่ยาวขึ้น

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

ไลบรารี. NET ทำหน้าที่เป็นตัวอย่างที่ยอดเยี่ยม ตัวอย่างเช่นเมื่อคุณเขียนฟังก์ชั่นที่ยอมรับIEnumerable<T>สิ่งที่คุณกำลังพูดคือ "ฉันไม่สนใจว่าคุณจะเก็บข้อมูลของคุณอย่างไรฉันแค่อยากรู้ว่าฉันสามารถใช้foreachลูปได้

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

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

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

องค์ประกอบมักจะเหมาะสมกว่าการสืบทอด ... [B] รหัสที่ใช้ในคลาสย่อยนั้นถูกกระจายระหว่างฐานและคลาสย่อยมันอาจเป็นเรื่องยากที่จะเข้าใจการใช้งาน คลาสย่อยไม่สามารถแทนที่ฟังก์ชันที่ไม่ใช่แบบเสมือนดังนั้นคลาสย่อยจะไม่สามารถเปลี่ยนการใช้งานได้

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

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

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

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

†เมื่อแสดงการสืบทอดทุกคนใช้สัตว์ นั่นคือไร้ประโยชน์ ในช่วง 11 ปีของการพัฒนาฉันไม่เคยเขียนชื่อคลาสCatดังนั้นฉันจะไม่ใช้มันเป็นตัวอย่าง


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

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

4

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

คุณอยู่ในกรณีนี้ที่ดีขึ้นจากพฤติกรรมที่เป็นนามธรรมเช่นนี้:

public interface IFoodEater
{
    void Eat(IFood food);
}

public class Animal : IFoodEater
{
    private IFoodProcessor _foodProcessor;
    public Animal(IFoodProcessor foodProcessor)
    {
        _foodProcessor = foodProcessor;
    }

    public void Eat(IFood food)
    {
        _foodProcessor.Process(food);
    }
}

หรือใช้รูปแบบผู้เข้าชมที่FoodProcessorพยายามเลี้ยงสัตว์ที่เข้ากันได้ ( ICarnivore : IFoodEater, MeatProcessingVisitor) ความคิดของสัตว์มีชีวิตอาจขัดขวางคุณในการคิดเกี่ยวกับการคัดลอกติดตามการย่อยอาหารของพวกเขาออกและแทนที่ด้วยสัตว์เลี้ยงทั่วไป

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

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


1
IFoodEater เป็นประเภทที่ซ้ำซ้อน คุณคาดหวังอะไรอีกที่จะสามารถกินอาหารได้? ใช่สัตว์เป็นตัวอย่างเป็นสิ่งที่น่ากลัว
ร่าเริง

1
พืชกินเนื้อเป็นอย่างไร? :) อย่างจริงจังประเด็นสำคัญข้อหนึ่งที่ไม่ได้ใช้อินเทอร์เฟซในกรณีนี้คือคุณได้เชื่อมโยงกับแนวคิดการกินกับสัตว์อย่างใกล้ชิดและมันก็เป็นงานอีกมากมายที่จะแนะนำ abstractions ใหม่ที่คลาสฐานสัตว์ไม่เหมาะสม
Julia Hayward

1
ฉันเชื่อว่า "กิน" เป็นความคิดที่ผิดที่นี่: ไปเป็นนามธรรมมากขึ้น วิธีการเกี่ยวกับIConsumerอินเตอร์เฟซ สัตว์กินเนื้อสามารถกินเนื้อสัตว์กินพืชกินพืชและพืชกินแสงแดดและน้ำ

2

อินเทอร์เฟซเป็นสัญญาจริง ๆ ไม่มีฟังก์ชั่น มันขึ้นอยู่กับผู้ดำเนินการที่จะใช้ ไม่มีทางเลือกว่าใครจะต้องปฏิบัติตามสัญญา

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

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

public abstract class Person
    {
        /// <summary>
        /// Inheritors must implement a hello
        /// </summary>
        /// <returns>Hello</returns>
        public abstract string SayHello();
        /// <summary>
        /// Inheritors can use base functionality, or override
        /// </summary>
        /// <returns></returns>
        public virtual string SayGoodBye()
        {
            return "Good Bye";
        }
        /// <summary>
        /// Base Functionality that is inherited 
        /// </summary>
        public void DoSomething()
        {
            //Do Something Here
        }
    }

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

ทีนี้ถ้าคำจำกัดความของ "การกิน" แตกต่างกันอย่างมากจากสัตว์สู่สัตว์บางทีอาจจะดีกว่า บางครั้งนี่เป็นพื้นที่สีเทาและขึ้นอยู่กับแต่ละสถานการณ์


1

เลขที่

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

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

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


รอทำไมอีกครั้งดำเนินการeat()ในทุกสัตว์? เราสามารถทำให้สำเร็จmammalได้ใช่ไหม ฉันเข้าใจประเด็นของคุณเกี่ยวกับข้อกำหนดต่างๆ
sbichenko

@exizt: ขออภัยฉันอ่านคำถามของคุณไม่ถูกต้อง
ร่าเริง

1

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

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

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


1

ฉันกำลังดูคำตอบสำหรับคำถามนี้ด้วยตัวเองเพื่อดูว่าคนอื่นพูดอย่างไร แต่ฉันจะพูดสามสิ่งที่ฉันอยากจะคิดเกี่ยวกับสิ่งนี้:

  1. ผู้คนใส่ศรัทธามากเกินไปในอินเทอร์เฟซและศรัทธาน้อยเกินไปในชั้นเรียน อินเทอร์เฟซนั้นเป็นคลาสพื้นฐานที่มีน้ำมากและบางครั้งเมื่อใช้บางสิ่งที่มีน้ำหนักเบาสามารถนำไปสู่สิ่งต่าง ๆ เช่นรหัสสำเร็จรูปที่มากเกินไป

  2. ไม่มีอะไรผิดปกติเลยในการเอาชนะวิธีการเรียกใช้super.method()ภายในและปล่อยให้ส่วนที่เหลือของร่างกายทำสิ่งที่ไม่เข้าไปยุ่งกับคลาสพื้นฐาน นี้ไม่ได้ละเมิดLiskov ชดเชยหลักการ

  3. ตามกฎของหัวแม่มือการมีความแข็งแกร่งและดื้อรั้นในวิศวกรรมซอฟต์แวร์เป็นความคิดที่ไม่ดีแม้ว่าบางสิ่งบางอย่างโดยทั่วไปจะเป็นแนวปฏิบัติที่ดีที่สุด

ดังนั้นฉันจะเอาสิ่งที่พูดด้วยเกลือเม็ดหนึ่ง


5
People put way too much faith into interfaces, and too little faith into classes.ตลก. ฉันมักจะคิดว่าสิ่งที่ตรงกันข้ามเป็นเรื่องจริง
Simon Bergot

1
"สิ่งนี้ไม่ได้ละเมิดหลักการทดแทน Liskov" แต่มันใกล้เคียงอย่างยิ่งที่จะกลายเป็นการละเมิด LSP ปลอดภัยกว่าดีกว่าขออภัย
ร่าเริง

1

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

อินเทอร์เฟซคือสัญญา

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

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


0

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

ฉันยังเห็นด้วยกับ @Panercrisis ว่ากฎง่ายๆควรเป็นแนวทางทั่วไป ไม่ใช่สิ่งที่ควรเขียนด้วยหิน ไม่ต้องสงสัยเลยว่าจะมีเวลาที่มันไม่ทำงาน


-1

การเชื่อมต่อเป็นประเภท ไม่มีการใช้งาน พวกเขาง่าย ๆ กำหนดสัญญาสำหรับการจัดการชนิดนั้น สัญญานี้จะต้องปฏิบัติตามโดยชั้นเรียนใด ๆ ที่ใช้อินเตอร์เฟซ

การใช้อินเตอร์เฟสและคลาส abstract / base ควรถูกกำหนดโดยข้อกำหนดการออกแบบ

คลาสเดียวไม่ต้องการอินเทอร์เฟซ

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

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

ชอบองค์ประกอบมากกว่ามรดก - ทั้งหมดนี้หายไป

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