มันเป็นนิสัยที่ไม่ดีที่ไม่ได้ใช้อินเตอร์เฟสหรือไม่? [ปิด]


40

ฉันใช้ส่วนต่อประสานที่ไม่ค่อยพบและพบได้ทั่วไปในรหัสอื่น

นอกจากนี้ฉันสร้างคลาสย่อยและคลาสพิเศษ (ในขณะที่สร้างคลาสของตัวเอง) ไม่ค่อยมีในรหัสของฉัน

  • มันเป็นสิ่งที่ไม่ดี?
  • คุณแนะนำให้เปลี่ยนสไตล์นี้ไหม
  • สไตล์นี้มีผลข้างเคียงหรือไม่?
  • เป็นเพราะฉันไม่ได้ทำงานในโครงการขนาดใหญ่หรือไม่?

10
ภาษานี้คืออะไร?

2
นอกจากภาษาอะไรกระบวนทัศน์นี้คืออะไร
อาร์มันโด

1
แนวคิดของภาษา "อินเทอร์เฟซ" เหนือกว่าหรือไม่ Java มีอินเทอร์เฟซในตัวชนิด แต่นักพัฒนา C ++ มักสร้างอินเตอร์เฟสด้วย (นำหน้าด้วย I) และพวกมันทั้งหมดมีจุดประสงค์เดียวกัน
Ricket

4
@Ricket: ไม่ไม่ได้จริงๆ ปัญหาคือ C ++ มีการสืบทอดหลายคลาส ใน C ++ "อินเตอร์เฟซ" เป็นแนวคิดมากขึ้นและในความเป็นจริงเราส่วนใหญ่ใช้สิ่งที่พวกเขาจะกำหนดเป็นคลาสฐานนามธรรม #
DeadMG

2
@Ricket ไม่โดยเฉพาะอย่างยิ่งถ้าคุณกำลังทำงานในภาษาที่ใช้งานได้ดีกว่าขั้นตอนหรือ OOP
ทางเลือก

คำตอบ:


36

มีสาเหตุหลายประการที่คุณอาจต้องการใช้ส่วนต่อประสาน:

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

http://msdn.microsoft.com/en-us/library/3b5b8ezk(v=vs.80).aspx

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


13
ภาษาของเขาไม่ได้ถูกระบุและคุณมีความเฉพาะเจาะจงมากเกินไปตัวอย่างเช่นใน C ++ โครงสร้างสามารถสืบทอดจากคลาสได้และคุณสามารถสืบทอดฐานที่ไม่ใช่นามธรรมได้
DeadMG

2
คำตอบนี้น่าจะเป็น C # แต่เกี่ยวข้องกับ Java ด้วยเช่นกันถ้าคุณนำออก # 4 และมีผลกับ C ++ เท่านั้นเนื่องจากมีการอนุญาตให้ใช้การสืบทอดหลายรายการใน C ++
Ricket

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

3
@Ricket: สาเหตุที่อยู่ในสัญลักษณ์หัวข้อย่อยสี่คำตอบของฉัน หากไม่มีเหตุผลดังกล่าวคุณก็ไม่จำเป็นต้องมีส่วนต่อประสาน
Robert Harvey

17

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

ฉันจะบอกว่าการใช้อินเทอร์เฟซให้บ่อยขึ้นนั้นไม่ใช่การฝึกฝนที่ไม่ดีเลย ตอนนี้ฉันกำลังอ่าน "Effective C # - 50 วิธีเฉพาะเพื่อปรับปรุง C # ของคุณ" โดย Bill Wagner หมายเลขสินค้า 22 ฯ และฉันพูด "ชอบกำหนดและใช้อินเทอร์เฟซการสืบทอด"

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

คู่ของคำพูดจากหนังสือ Bill Wagners ...

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

"อินเทอร์เฟซการเข้ารหัสมอบความยืดหยุ่นให้กับนักพัฒนาอื่น ๆ มากกว่าการเข้ารหัสในประเภทคลาสพื้นฐาน"

"การใช้อินเทอร์เฟซเพื่อกำหนด API สำหรับคลาสให้ความยืดหยุ่นที่มากขึ้น"

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

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


2
ฉันอ่านมันและคิดว่ามันเป็นคำตอบที่ดี
Eric King

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

@ ฉันไม่ได้บอกว่าคำตอบนั้นไม่ดี แต่เพียงว่ามันยาวไปหน่อย ฉันสงสัยว่าคุณต้องการใบเสนอราคาทั้งหมดในบางครั้ง
kevinji

3
ฮ่าฮ่าถ้านี่คือ tl; dr ฉันไม่แน่ใจว่าคุณจะชอบสิ่งนี้ทั้งหมดของ StackExchange-high-quality-answer
Nic

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

8

สิ่งหนึ่งที่ไม่ได้กล่าวถึงคือการทดสอบใน C # mocking-libraries ทั้งหมดรวมถึง Java สำหรับบางคลาสไม่สามารถเยาะเย้ยคลาสได้เว้นแต่จะใช้อินเตอร์เฟส สิ่งนี้นำไปสู่โครงการจำนวนมากที่ติดตามการปฏิบัติของ Agile / TDDเพื่อมอบอินเทอร์เฟซของตนเองในทุกระดับ

บางคนพิจารณาวิธีปฏิบัติที่ดีที่สุดนี้เพราะมัน "ลดการแต่งงานกัน" แต่ฉันไม่เห็นด้วย - ฉันคิดว่ามันเป็นเพียงวิธีแก้ปัญหาการขาดภาษา


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

ตัวอย่างเช่นกรอบงาน. Net มีหลายคลาสที่เก็บรายการสิ่งต่าง ๆแต่พวกเขาทั้งหมดเก็บสิ่งนั้นด้วยวิธีที่ต่างกัน ดังนั้นจึงเหมาะสมที่จะมีIList<T>อินเตอร์เฟสนามธรรมซึ่งสามารถนำไปใช้งานได้โดยใช้วิธีการที่แตกต่างกัน

คุณควรใช้มันเมื่อคุณต้องการให้ 2+ คลาสเปลี่ยนได้หรือเปลี่ยนได้ในอนาคต หากวิธีการใหม่ของการจัดเก็บรายการออกมาในอนาคตAwesomeList<T>จากนั้นสมมติว่าคุณใช้IList<T>ตลอดทั้งรหัสของคุณการเปลี่ยนไปใช้AwesomeList<T>จะหมายถึงการเปลี่ยนเพียงไม่กี่บรรทัดโหลไม่กี่แสน / พัน


5

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


4

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

ปัญหาคือในภาษาเช่น C # และ Java ที่มีชื่อสามัญแบบรวบรวมเวลาอ่อนคุณสามารถจบลงด้วยการละเมิด DRY เนื่องจากไม่มีวิธีการเขียนวิธีหนึ่งที่สามารถจัดการกับมากกว่าหนึ่งคลาสยกเว้นคลาสทั้งหมดสืบทอดจากฐานเดียวกัน แม้ว่าC # 4 dynamic สามารถจัดการกับสิ่งนี้ได้

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


1
เหตุผลสำหรับการลงคะแนน?
DeadMG

ไม่แน่ใจมี upvote มันเป็นคำตอบที่ดี
ไบรอัน Boettcher

3

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


3

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

ดังนั้นคำถามของคุณนั้นขึ้นอยู่กับกระบวนทัศน์ (OOP ในกรณีนี้) และยิ่งไปกว่านั้นขึ้นอยู่กับภาษามาก ๆ (มีภาษา OOP ที่ไม่มีส่วนต่อประสาน)


2

หากคุณกำลังพูดถึงการใช้ Java เหตุผลหนึ่งในการใช้งานอินเทอร์เฟซคือพวกเขาเปิดใช้งานการใช้งานของวัตถุพร็อกซี่โดยไม่ต้องสร้างรหัสห้องสมุด นี่อาจเป็นข้อได้เปรียบที่สำคัญเมื่อคุณทำงานกับกรอบที่ซับซ้อนเช่นฤดูใบไม้ผลิ ยิ่งไปกว่านั้นฟังก์ชั่นบางอย่างต้องการอินเทอร์เฟซ: RMI เป็นตัวอย่างคลาสสิกของสิ่งนี้เนื่องจากคุณต้องอธิบายถึงฟังก์ชั่นที่คุณให้ในแง่ของอินเทอร์เฟซ (ที่สืบทอดมาจากjava.rmi.Remote)


1

สิ่งต่าง ๆ กำลังเปลี่ยนไป ( MS Moles ) แต่เหตุผลหลักที่ฉันคิดว่ามันเป็นเรื่องดีที่จะเขียนโค้ดให้เฉพาะกับส่วนต่อประสานก็คือมันง่ายต่อการเยาะเย้ยและปรับให้เข้ากับสถาปัตยกรรม IoC

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


0

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

แย่:

class B; // forward declaration
class A
{
  B* b;
};

class B
{
  A* a;
}

ไม่เลว:

class PartOfClassB_AccessedByA
{
};

class A
{
  PartOfClassB_AccessedByA* b;
};

class B : public PartOfClassB_AccessedByA
{
  A* a;
}

โดยปกติ A, B, PartOfClassB_AccessedByA ถูกนำไปใช้กับไฟล์แยกต่างหาก


0

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

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