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


243

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



16
หรือเพื่อให้ง่ายต่อการ unittest

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

2
อภิปรายใหม่ในคำถามนี้
yannis

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

คำตอบ:


205

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

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


84
การทดสอบ +1 หมายความว่าคุณมีการใช้งานสองครั้งอยู่แล้ว
jk

24
@YannisRizos ไม่เห็นด้วยกับจุดหลังของคุณเนื่องจาก Yagni การทำอินเทอร์เฟซจากวิธีสาธารณะในชั้นเรียนนั้นเป็นเรื่องเล็กน้อยหลังจากข้อเท็จจริงเช่นเดียวกับการแทนที่ CFoo ด้วย IFoo ในชั้นเรียนที่ต้องใช้ ไม่มีจุดในการเขียนไว้ล่วงหน้าก่อนความต้องการ
Dan Neely

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

6
ฉันคิดว่าอินเทอร์เฟซที่ขาดหายไปไม่ใช่ตัวอย่างที่ดีสำหรับ YAGNI แต่สำหรับ "หน้าต่างที่แตก" และเอกสารที่หายไป ผู้ใช้งานของชั้นเรียนถูกบังคับให้ใช้รหัสเพื่อต่อต้านการนำไปปฏิบัติแทนที่จะเป็นสิ่งที่ควรทำ
Fabio Fracassi

10
ทำไมคุณถึงสร้างมลภาวะให้กับ codebase ของคุณด้วย cruft ที่ไม่มีความหมายเพื่อให้เป็นไปตามกรอบการทดสอบ ฉันหมายถึงอย่างจริงจังฉันเป็นคนที่เรียนรู้ด้วยตัวเองด้วย JavaScript ที่พยายามเรียงลำดับ WTF ผิดกับ C # และการใช้งาน OOD ของผู้พัฒนา Java ที่ฉันพบเจอในการแสวงหาการเป็น Generalist ที่รอบรู้มากขึ้น พวกเขาจะตบไอดีออกจากมือของคุณและอย่าให้มันกลับมาจนกว่าคุณจะเรียนรู้วิธีการเขียนโค้ดที่สะอาดและอ่านง่ายเมื่อพวกเขาจับคุณทำสิ่งนั้นในวิทยาลัย? นั่นเป็นเพียงลามกอนาจาร
Erik Reppen

144

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

นี่เป็นกฎง่ายๆข้อหนึ่งที่มีประโยชน์มาก:

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

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

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


14
ฉันจะโหวตมากกว่านี้มากกว่าหนึ่งครั้งถ้าทำได้ IMO คำตอบที่ดีที่สุดสำหรับคำถามนี้
Fabio Fracassi

4
หากคลาสดำเนินการเพียงบทบาทเดียว (เช่นจะมีเพียงหนึ่งอินเตอร์เฟสที่กำหนดไว้) ดังนั้นทำไมไม่ทำเช่นนี้ผ่านวิธีการแบบสาธารณะ / ส่วนตัว?
Matthew Finlay

4
1. การจัดระเบียบรหัส การมีส่วนต่อประสานในไฟล์ของตัวเองที่มีเพียงลายเซ็นและความคิดเห็นเอกสารประกอบช่วยให้รหัสสะอาด 2. เฟรมเวิร์กที่บังคับให้คุณเปิดเผยวิธีที่คุณต้องการเก็บไว้เป็นส่วนตัว (เช่นคอนเทนเนอร์ที่ฉีดการพึ่งพาผ่านเซ็ตสาธารณะ) 3. แยกการรวบรวมตามที่ได้กล่าวไปแล้ว; ถ้าระดับFooขึ้นอยู่กับอินเตอร์เฟซที่Barแล้วสามารถแก้ไขได้โดยไม่ต้องคอมไพล์BarImpl Foo4. การควบคุมการเข้าถึงที่ละเอียดยิ่งขึ้นกว่าข้อเสนอสาธารณะ / ส่วนตัว (เปิดเผยคลาสเดียวกันกับลูกค้าสองรายผ่านส่วนต่อประสานที่แตกต่างกัน)
sacundim

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

4
@sacundim If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImplเกี่ยวกับการเปลี่ยน BarImpl การเปลี่ยนแปลงที่สามารถหลีกเลี่ยงได้ใน Foo โดยใช้interface Barคืออะไร? เนื่องจากตราบใดที่ลายเซ็นและการทำงานของวิธีการไม่เปลี่ยนแปลงใน BarImpl, Foo ไม่ต้องการการเปลี่ยนแปลงแม้ไม่มีอินเทอร์เฟซ (วัตถุประสงค์ทั้งหมดของอินเทอร์เฟซ) ฉันกำลังพูดถึงสถานการณ์ที่มีเพียงคลาสเดียวเท่านั้นที่BarImplใช้แถบ สำหรับสถานการณ์หลายชั้นฉันเข้าใจหลักการผกผันของการพึ่งพาและวิธีการอินเทอร์เฟซที่เป็นประโยชน์
Shishir Gupta

101

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

ไม่จำเป็นต้องกำหนดอินเทอร์เฟซถ้าคุณรู้ว่าพฤติกรรมที่กำหนดโดยจะใช้เพียงครั้งเดียว KISS (ทำให้ง่ายโง่)


ไม่เสมอไปคุณสามารถใช้อินเทอร์เฟซในคลาสเดียวถ้าคลาสนั้นเป็นคลาสทั่วไป
John Isaiah Carmona

นอกจากนี้คุณสามารถแนะนำอินเทอร์เฟซในที่สุดเมื่อต้องการได้อย่างง่ายดายใน IDE ที่ทันสมัยด้วยการปรับโครงสร้าง "แยกอินเทอร์เฟซ"
Hans-Peter Störr

พิจารณาคลาสยูทิลิตี้ที่มีเพียงวิธีคงที่สาธารณะและตัวสร้างส่วนตัว - ยอมรับอย่างสมบูรณ์และได้รับการส่งเสริมโดยผู้เขียนเช่น Joshua Bloch และคณะ
Darrell Teague

63

ในทางทฤษฎีแล้วคุณไม่ควรมีอินเทอร์เฟซเพียงเพื่อประโยชน์ของอินเทอร์เฟซ แต่คำตอบของ Yannis Rizos จะบอกกล่าวกับภาวะแทรกซ้อนเพิ่มเติม:

เมื่อคุณเขียนการทดสอบหน่วยและใช้เฟรมเวิร์กจำลองเช่น Moq หรือ FakeItEasy (เพื่อตั้งชื่อสองรายการล่าสุดที่ฉันเคยใช้) คุณจะสร้างคลาสอื่นที่ใช้อินเทอร์เฟซโดยปริยาย การค้นหารหัสหรือการวิเคราะห์แบบคงที่อาจอ้างว่ามีเพียงการใช้งานเพียงอย่างเดียว แต่ในความเป็นจริงแล้วมีการใช้งานการจำลองภายใน เมื่อใดก็ตามที่คุณเริ่มเขียน mocks คุณจะพบว่าการแยกอินเทอร์เฟซเหมาะสม

แต่เดี๋ยวก่อนยังมีอีกมาก มีสถานการณ์เพิ่มเติมที่มีการใช้อินเทอร์เฟซโดยนัย ตัวอย่างเช่นการใช้ WCF การสื่อสารสแต็กของ. NET สร้างพร็อกซีไปยังบริการระยะไกลซึ่งใช้อินเทอร์เฟซอีกครั้ง

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


2
+1: ฉันไม่แน่ใจว่า YAGNI ใช้ที่นี่เนื่องจากมีอินเทอร์เฟซและใช้เฟรมเวิร์ก (เช่น JMock ฯลฯ ) ที่ใช้อินเทอร์เฟซสามารถประหยัดเวลาของคุณได้จริง
เด

4
@Deco: กรอบการเยาะเย้ยที่ดี (JMock ในหมู่พวกเขา) ไม่จำเป็นต้องมีอินเตอร์เฟซ
Michael Borgwardt

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

8
ฉันจะบอกว่ามันเป็นวิธีอื่น ๆ การใช้วัตถุจำลองในการทดสอบหมายถึงการสร้างการใช้งานทางเลือกในการเชื่อมต่อของคุณตามคำนิยาม สิ่งนี้เป็นจริงไม่ว่าคุณจะสร้างคลาส FakeImplementation ของคุณเองหรือปล่อยให้การเยาะเย้ยเฟรมเวิร์กเป็นการยกที่หนักหน่วงสำหรับคุณ อาจมีเฟรมเวิร์กบางอย่างเช่น EasyMock ที่ใช้แฮ็กต่างๆและลูกเล่นเลเวลต่ำเพื่อเลียนแบบคลาสที่เป็นรูปธรรม - และเพิ่มพลังให้พวกมัน! - แต่ในเชิงแนวคิดแล้ววัตถุจำลองเป็นการนำไปใช้ทางเลือกในการทำสัญญา
Avner Shahar-Kashtan

1
คุณไม่ได้ทำการทดสอบในการผลิต ทำไมการจำลองสำหรับคลาสที่ไม่ต้องการอินเทอร์เฟซต้องการอินเทอร์เฟซ
Erik Reppen

32

ไม่คุณไม่ต้องการมันและฉันคิดว่ามันเป็น anti-pattern เพื่อสร้างอินเตอร์เฟสสำหรับการอ้างอิงทุกคลาสโดยอัตโนมัติ

มีค่าใช้จ่ายจริงในการทำ Foo / FooImpl สำหรับทุกสิ่ง IDE อาจสร้างอินเตอร์เฟส / การนำไปใช้งานฟรี แต่เมื่อคุณนำทางรหัสคุณมีภาระการรับรู้พิเศษจาก F3 / F12 ในfoo.doSomething()การนำคุณไปสู่ลายเซ็นของอินเทอร์เฟซไม่ใช่การใช้งานจริงที่คุณต้องการ นอกจากนี้คุณยังมีไฟล์สองไฟล์แทนที่จะเป็นไฟล์เดียวสำหรับทุกสิ่ง

ดังนั้นคุณควรจะทำก็ต่อเมื่อคุณต้องการสิ่งนั้นจริงๆ

ตอนนี้ที่อยู่โต้เถียง:

ฉันต้องการอินเทอร์เฟซสำหรับกรอบการฉีดที่พึ่งพา

อินเทอร์เฟซสำหรับการสนับสนุนเฟรมเวิร์กเป็นแบบดั้งเดิม ใน Java อินเทอร์เฟซที่ใช้เป็นข้อกำหนดสำหรับพร็อกซีแบบไดนามิก pre-CGLIB วันนี้คุณไม่ต้องการมัน ถือว่าเป็นความคืบหน้าและประโยชน์สำหรับนักพัฒนาซอฟต์แวร์ที่คุณไม่ต้องการอีกต่อไปใน EJB3, Spring ฯลฯ

ฉันต้องการ mocks สำหรับการทดสอบหน่วย

หากคุณเขียน mocks ของคุณเองและมีสองการใช้งานจริงแล้วอินเตอร์เฟซที่เหมาะสม เราอาจจะไม่ได้พูดคุยกันในตอนแรกถ้า codebase ของคุณมีทั้ง FooImpl และ TestFoo

แต่ถ้าคุณใช้กรอบการเยาะเย้ยเช่น Moq, EasyMock หรือ Mockito คุณสามารถเยาะเย้ยคลาสและไม่ต้องการอินเทอร์เฟซ มันคล้ายกับการตั้งค่าfoo.method = mockImplementationในภาษาแบบไดนามิกที่สามารถกำหนดวิธีการได้

เราต้องการอินเทอร์เฟซเพื่อปฏิบัติตามหลักการผกผันของการพึ่งพา (DIP)

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

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

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

ดูสิ่งนี้ด้วย:


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

4
ฉันไม่ได้พบปัญหากับคลาสที่เยาะเย้ย แต่ฉันก็รู้สึกหงุดหงิดจากการนำทาง IDE ไม่ทำงานอย่างที่คาดไว้ การแก้ปัญหาที่เกิดขึ้นจริงเป็นสิ่งที่สมมุติขึ้น
wrschneider

1
@Jules คุณสามารถจำลองคลาสที่เป็นรูปธรรมรวมถึงตัวสร้างใน Java
assylias

1
@assylias คุณจะป้องกันไม่ให้นวกรรมิกทำงานได้อย่างไร?
จูลส์

2
@Jules มันขึ้นอยู่กับกรอบการเยาะเย้ยของคุณ - ด้วย jmockit ตัวอย่างเช่นคุณสามารถเขียนnew Mockup<YourClass>() {}และทั้งคลาสรวมถึงคอนสตรัคเตอร์ของมันถูกเยาะเย้ยไม่ว่าจะเป็นอินเทอร์เฟซคลาสนามธรรมหรือคลาสคอนกรีต นอกจากนี้คุณยังสามารถ "แทนที่" พฤติกรรมของตัวสร้างหากคุณต้องการ ฉันคิดว่ามีวิธีที่เท่าเทียมกันใน Mockito หรือ Powermock
assylias

22

ดูเหมือนว่าคำตอบของรั้วทั้งสองด้านสามารถสรุปได้ในเรื่องนี้:

ออกแบบให้ดีและใส่อินเตอร์เฟสที่จำเป็นต้องใช้อินเตอร์เฟส

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

ตัวอย่างเช่น (มีแผนที่น่ากลัว) สมมติว่าคุณกำลังสร้างCarชั้นเรียน ในชั้นเรียนของคุณคุณจะต้องมีเลเยอร์ UI ในตัวอย่างนี้โดยเฉพาะอย่างยิ่งจะต้องใช้รูปแบบของIginitionSwitch, SteeringWheel, GearShift, และGasPedal BrakePedalเนื่องจากรถคันนี้มีคุณไม่จำเป็นต้องมีAutomaticTransmission ClutchPedal(และเนื่องจากนี่เป็นรถที่แย่มากไม่มีเครื่องปรับอากาศวิทยุหรือเบาะนั่งตามจริงแล้วพื้นกระดานก็หายไปเช่นกัน - คุณแค่ต้องไปที่พวงมาลัยและหวังว่าจะดีที่สุด!)

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

คุณสามารถมีส่วนต่อประสานที่มีลักษณะดังนี้:

Interface ICabin
    Event IgnitionSwitchTurnedOn()
    Event IgnitionSwitchTurnedOff()
    Event BrakePedalPositionChanged(int percent)
    Event GasPedalPositionChanged(int percent)
    Event GearShiftGearChanged(int gearNum)
    Event SteeringWheelTurned(float degree)
End Interface

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

เป็นเหตุผลเดียวกับที่คุณไม่ได้ทดสอบสมาชิกส่วนตัว - มีเพียงเพื่อสนับสนุน API สาธารณะและพฤติกรรมของพวกเขาควรได้รับการทดสอบโดยการทดสอบ API

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


แก้ไข:

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

ดินแดนที่ใช้อำนาจหรืออำนาจเหนือ; สมบัติของอธิปไตยหรือเครือจักรภพหรือไม่ชอบ ยังใช้เปรียบเปรย [WordNet sense 2] [1913 เว็บสเตอร์]

ในแง่ของตัวอย่างของฉัน - IgnitionSwitchขอพิจารณา ในรถยนต์ meatspace สวิตช์กุญแจจุดระเบิดรับผิดชอบ:

  1. การพิสูจน์ตัวตน (ไม่ระบุ) ผู้ใช้ (พวกเขาต้องการรหัสที่ถูกต้อง)
  2. จัดหากระแสไฟให้กับสตาร์ทเตอร์เพื่อให้สามารถสตาร์ทรถได้จริง
  3. ให้กระแสกับระบบจุดระเบิดเพื่อให้สามารถทำงานต่อไปได้
  4. ปิดกระแสไฟเพื่อให้รถหยุด
  5. ในรถยนต์รุ่นใหม่ส่วนใหญ่ (ทั้งหมด?) จะมีสวิตช์ที่ป้องกันไม่ให้กุญแจถูกลบออกจากการจุดระเบิดในขณะที่การส่งสัญญาณไม่ได้อยู่ใน Park ดังนั้นนี่อาจเป็นส่วนหนึ่งของโดเมน (จริงๆแล้วมันหมายถึงฉันต้องคิดใหม่และออกแบบระบบของฉัน ... )

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

ไม่ได้เป็นผู้รับผิดชอบในการIgnitionSwitch GasPedalสวิตช์จุดระเบิดนั้นไม่มีส่วนสำคัญใด ๆ กับคันเร่งในทุก ๆ ทาง พวกเขาทำงานเป็นอิสระอย่างสมบูรณ์จากกัน (แม้ว่ารถจะไม่มีราคาพอสมควรโดยไม่ต้องทั้งคู่!)

ตามที่ฉันกล่าวไว้ในตอนแรกมันขึ้นอยู่กับการออกแบบของคุณ คุณสามารถออกแบบIgnitionSwitchที่มีสองค่า: เปิด ( True) และปิด ( False) หรือคุณสามารถออกแบบเพื่อรับรองความถูกต้องของคีย์ที่ให้ไว้และโฮสต์ของการดำเนินการอื่น ๆ นั่นคือส่วนที่ยากของการเป็นนักพัฒนาที่จะตัดสินใจว่าจะวาดเส้นบนผืนทราย - และโดยส่วนใหญ่แล้วจะสัมพันธ์กันอย่างสมบูรณ์ บรรทัดเหล่านั้นในทรายนั้นมีความสำคัญ - นั่นคือที่ที่ API ของคุณอยู่และสิ่งที่ส่วนต่อประสานของคุณควรอยู่


คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับสิ่งที่คุณหมายถึงโดย "มีโดเมนของตัวเอง" ได้ไหม?
Lamin Sanneh

1
@ LaminSanneh ทำอย่างละเอียด มันช่วยได้ไหม
Wayne Werner

8

ไม่(YAGNI)ยกเว้นว่าคุณวางแผนที่จะเขียนการทดสอบสำหรับคลาสอื่น ๆ ที่ใช้อินเทอร์เฟซนี้และการทดสอบเหล่านั้นจะได้รับประโยชน์จากการเยาะเย้ยอินเทอร์เฟซ


8

จากMSDN :

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

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

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

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

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


5

เพื่อตอบคำถาม: มีมากกว่านั้น

สิ่งสำคัญอย่างหนึ่งของอินเทอร์เฟซคือเจตนา

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

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


5

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

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

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

แม้ว่าโมดูล B จะมีการใช้อินเตอร์เฟสเพียงหนึ่งอินเทอร์เฟซก็ยังจำเป็น

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


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

4

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

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

interface NameChangeListener { // Implemented by a lot of people
    void nameChanged(String name); 
} 

interface NameChangeCount { // Only implemented by my class
    int getCount();
}

class NameChangeCounter implements NameChangeListener, NameChangeCount {
    ...
}

class SomeUserInterface {
    private NameChangeCount currentCount; // Will never know that you can change the counter
}

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

interface SomeRepository { // Guarantee that the external library details won't leak trough
    ...
}

class OracleSomeRepository implements SomeRepository { 
    ... // Oracle prefix allow us to quickly know what is going on in this class
}

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

package project.domain;

interface UserRequestSource {
    public UserRequest getLastRequest();
}

class UserBehaviorAnalyser {
    private UserRequestSource requestSource;
}

package project.ui;

class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
    // UI concern, no need for my domain object to know about this method.
    public void displayLabelInErrorMode(); 

    // They most certainly need to know about *that* though
    public UserRequest getLastRequest();
}

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

interface Sender {
    void sendMessage(Message message)
}

class PacketSender implements Sender {
    void sendMessage(Message message);
    void setPacketSize(int sizeInByte);
}

class Throttler { // This class need to have full access to the object
    private PacketSender sender;

    public useLowNetworkUsageMode() {
        sender.setPacketSize(LOW_PACKET_SIZE);
        sender.sendMessage(new NotifyLowNetworkUsageMessage());

        ... // Other details
    }
}

class MailOrder { // Not this one though
    private Sender sender;
}

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


2

อินเทอร์เฟซสำคัญมาก แต่พยายามควบคุมจำนวนที่คุณมี

การเริ่มต้นสร้างอินเทอร์เฟซสำหรับทุกสิ่งมันเป็นเรื่องง่ายที่จะลงเอยด้วยรหัส 'สปาเก็ตตี้สับ' ฉันคล้อยตามภูมิปัญญาที่ยิ่งใหญ่ของ Ayende Rahien ที่ได้โพสต์คำพูดที่ฉลาดมากบางเรื่อง:

http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application

นี่คือโพสต์แรกของซีรีส์ทั้งชุดของเขาดังนั้นอ่านต่อไป!


โค้ด 'chopped-up spaghetti' หรือที่รู้จักกันในชื่อravioli
CurtainDog

ฟังดูเหมือน lasagne-code หรือ baklava-code ถึงฉัน - รหัสที่มีเลเยอร์มากเกินไป ;-)
dodgy_coder

2

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


2

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


1

ไม่จำเป็นต้องกำหนดอินเตอร์เฟสสำหรับคลาสเสมอไป

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

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

ดังนั้นฉันจะกำหนดอินเทอร์เฟซสำหรับคลาสระดับสูงที่คุณต้องการให้นามธรรมจากการใช้งานเท่านั้น

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

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