หลักการผกผันของการพึ่งพาคืออะไรและเหตุใดจึงสำคัญ?


178

หลักการผกผันของการพึ่งพาคืออะไรและเหตุใดจึงสำคัญ?



3
จำนวนคำตอบไร้สาระที่นี่ใช้คำว่า "ระดับสูง" และ "ระดับต่ำ" จาก Wikipedia คำเหล่านี้ไม่สามารถเข้าถึงได้และนำผู้อ่านจำนวนมากมายังหน้านี้ หากคุณกำลังจะสำรอก wikipedia อีกครั้งโปรดกำหนดคำเหล่านี้ในคำตอบของคุณเพื่อให้เข้าใจ
8bitjunkie

คำตอบ:


107

ตรวจสอบเอกสารนี้ออก: พึ่งพาผกผันหลักการ

โดยพื้นฐานแล้วมันพูดว่า:

  • โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองควรขึ้นอยู่กับ abstractions
  • บทคัดย่อไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดควรขึ้นอยู่กับ abstractions

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

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

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


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

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

6
คลาส A ที่ใช้คลาส B ไม่ได้หมายความว่า "ขึ้นกับ" ในการใช้งาน B; มันขึ้นอยู่กับอินเตอร์เฟสสาธารณะของ B เท่านั้น การเพิ่มนามธรรมที่แยกต่างหากซึ่งทั้ง A และ B ขึ้นอยู่กับตอนนี้หมายความว่า A จะไม่มีการพึ่งพาเวลารวบรวมบนส่วนต่อประสานสาธารณะของ B. การแยกระหว่างหน่วยสามารถทำได้อย่างง่ายดาย มีเครื่องมือการเยาะเย้ยเฉพาะสำหรับสิ่งนั้นใน Java และ. NET ที่จัดการกับทุกสถานการณ์ (วิธีการคงที่ตัวสร้าง ฯลฯ ) การใช้งาน DIP มีแนวโน้มที่จะทำให้ซอฟต์แวร์มีความซับซ้อนมากขึ้นและบำรุงรักษาได้น้อยลงและไม่สามารถทดสอบได้อีกต่อไป
Rogério

1
แล้วการผกผันของการควบคุมคืออะไร? วิธีในการบรรลุการพึ่งพาการผกผัน?
user20358

2
@Rogerio ดูคำตอบ Derek Greer ด้านล่างเขาอธิบาย AFAIK กรมทรัพย์สินทางปัญญากล่าวว่ามันคือ A ที่กำหนดสิ่งที่ A ต้องการไม่ใช่ B ที่บอกว่า A ต้องการอะไร ดังนั้นอินเทอร์เฟซที่ A ไม่ควรได้รับจาก B แต่สำหรับ A.
ejaenv

145

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

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

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

สิ่งที่หลักการการพึ่งพาการพึ่งพาไม่ได้อ้างถึงคือการฝึกฝนอย่างง่ายของการพึ่งพาแบบนามธรรมผ่านการใช้อินเตอร์เฟส (เช่น MyService → [ILogger ⇐ Logger]) แม้ว่าสิ่งนี้จะแยกส่วนประกอบจากรายละเอียดการใช้งานเฉพาะของการพึ่งพา แต่ก็ไม่ได้กลับความสัมพันธ์ระหว่างผู้บริโภคและการพึ่งพา (เช่น [MyService → IMyServiceLogger] ⇐ Logger

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

ภายในเป้าหมายทั่วไปของการใช้ซ้ำเราสามารถแยกประเภทการใช้ซ้ำสองประเภท:

  1. การใช้ส่วนประกอบซอฟต์แวร์ภายในแอพพลิเคชั่นหลายตัวพร้อมกับการนำไปใช้งานแบบ sub-dependency (เช่นคุณได้พัฒนา DI container และต้องการจัดทำบันทึกข้อมูล แต่ไม่ต้องการเชื่อมโยงคอนเทนเนอร์ของคุณเข้ากับตัวบันทึกเฉพาะเช่นทุกคนที่ใช้คอนเทนเนอร์ของคุณ ใช้ไลบรารีการบันทึกที่คุณเลือก)

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

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

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

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

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

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


17
ขอบคุณ ฉันเห็นตอนนี้ว่าคำตอบของฉันพลาดจุดนี้อย่างไร ความแตกต่างระหว่างMyService → [ILogger ⇐ Logger]และ[MyService → IMyServiceLogger] ⇐ Loggerนั้นบอบบาง แต่สำคัญ
Patrick McElhaney

2
ในบรรทัดเดียวกันมันอธิบายได้ดีมากที่นี่: lostechies.com/derickbailey/2011/09/22/…
ejaenv

1
ฉันอยากให้คนหยุดใช้ loggers เป็น usecase ที่เป็นที่ยอมรับสำหรับการฉีดพึ่งพา โดยเฉพาะอย่างยิ่งในการเชื่อมต่อกับ log4net ซึ่งเป็นรูปแบบการต่อต้านเกือบ ที่นอกเหนือคำอธิบาย kickass!
Casper Leon Nielsen

4
@ Casper Leon Nielsen - กรมทรัพย์สินทางปัญญาไม่มีส่วนเกี่ยวข้องกับ DI พวกเขาไม่ใช่คำพ้องความหมายหรือแนวคิดที่เทียบเท่ากัน
TSmith

4
@ VF1 ตามที่ระบุไว้ในวรรคสรุปความสำคัญของหลักการการผกผันของการพึ่งพาอาศัยกันเป็นหลักด้วยการใช้ซ้ำ ถ้า John เผยแพร่ไลบรารี่ที่มีการพึ่งพาภายนอกในไลบรารีการบันทึกและแซมประสงค์จะใช้ไลบรารี่ของ John แซมจะใช้การพึ่งพาการบันทึกชั่วคราว แซมจะไม่สามารถปรับใช้แอปพลิเคชันของเขาได้หากไม่ได้เลือกไลบรารีการบันทึก John อย่างไรก็ตามหากจอห์นติดตามการใช้งานของแซมแซมมีอิสระที่จะจัดหาอะแดปเตอร์และใช้ไลบรารีการบันทึกที่เขาเลือก กรมทรัพย์สินทางปัญญาไม่ได้เกี่ยวกับความสะดวกสบาย แต่มีเพศสัมพันธ์
Derek Greer

14

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

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

หลักการผกผันของการพึ่งพานั้นระบุว่า:

  • โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองควรขึ้นอยู่กับ abstractions
  • บทคัดย่อไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดควรขึ้นอยู่กับ abstractions

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


11

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

สถาปัตยกรรมแบบดั้งเดิม

ตามเนื้อผ้า UI สถาปัตยกรรมชั้นขึ้นอยู่กับชั้นธุรกิจและสิ่งนี้จะขึ้นอยู่กับชั้นการเข้าถึงข้อมูล

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

เราจะมีไลบรารีหรือแพ็คเกจสำหรับ data access layer

// DataAccessLayer.dll
public class ProductDAO {

}

และตรรกะทางธุรกิจอื่น ๆ ของไลบรารีหรือแพคเกจเลเยอร์แพ็กเกจที่ขึ้นอยู่กับชั้นการเข้าถึงข้อมูล

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

สถาปัตยกรรมแบบเลเยอร์ที่มีการผกผันของการพึ่งพา

การผกผันของการพึ่งพาแสดงต่อไปนี้:

โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองควรขึ้นอยู่กับ abstractions

บทคัดย่อไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดควรขึ้นอยู่กับ abstractions

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

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

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

ลองจินตนาการว่าเราปรับรหัสของเราดังนี้:

เราจะมีไลบรารีหรือแพ็คเกจสำหรับ data access layer ซึ่งกำหนดสิ่งที่เป็นนามธรรม

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

และตรรกะทางธุรกิจอื่น ๆ ของไลบรารีหรือแพคเกจเลเยอร์แพ็กเกจที่ขึ้นอยู่กับชั้นการเข้าถึงข้อมูล

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

แม้ว่าเราจะขึ้นอยู่กับการพึ่งพานามธรรมระหว่างการเข้าถึงธุรกิจและข้อมูลยังคงเหมือนเดิม

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

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

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

หลังจากเลเยอร์การคงอยู่ขึ้นอยู่กับโดเมนการกลับด้านตอนนี้หากมีการกำหนดการอ้างอิง

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(ที่มา: xurxodev.com )

หลักการที่ลึกซึ้งยิ่งขึ้น

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

แต่ทำไมเราถึงต้องพึ่งการพึ่งพา? อะไรคือวัตถุประสงค์หลักนอกเหนือจากตัวอย่างที่เฉพาะเจาะจง?

โดยทั่วไปแล้วอนุญาตให้สิ่งที่มีเสถียรภาพมากที่สุดซึ่งไม่ได้ขึ้นอยู่กับสิ่งที่มีเสถียรภาพน้อยกว่าที่จะเปลี่ยนบ่อยขึ้น

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

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

สถาปัตยกรรม

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

สถาปัตยกรรมที่สะอาด

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


(ที่มา: 8thlight.com )

สถาปัตยกรรมหกเหลี่ยม

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


ฉันไม่แน่ใจว่ามันควรจะพูดอะไร แต่ประโยคนี้ไม่ต่อเนื่องกัน: "แต่ตามแนวทางที่เราใช้เราอาจเป็นการลงทุนที่ไม่เหมาะสมขึ้นอยู่กับการพึ่งพา แต่เป็นนามธรรม" คุณต้องการแก้ไขไหม
Mark Amery

9

สำหรับฉันหลักการพึ่งพาการพึ่งพา (Dependency Inversion Principle) ดังที่อธิบายไว้ในบทความอย่างเป็นทางการเป็นความพยายามที่ผิดพลาดจริง ๆ ในการเพิ่มความสามารถในการนำโมดูลกลับมาใช้ซ้ำซึ่งนำมาใช้ซ้ำได้น้อยลง

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

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

ดังนั้นการสร้างสิ่งที่เป็นนามธรรมแยกในระดับเดียวกันขององค์ประกอบที่ขึ้นอยู่กับองค์ประกอบ B (ซึ่งไม่ได้ขึ้นอยู่กับ A) สามารถทำได้เฉพาะในกรณีที่องค์ประกอบ A จะเป็นประโยชน์จริงๆสำหรับการใช้งานในบริบทที่แตกต่างกัน หากไม่ใช่ในกรณีนั้นการใช้งาน DIP จะเป็นการออกแบบที่ไม่ดี


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

3
กรมทรัพย์สินทางปัญญามีความสำคัญในหลายภาษาและไม่มีส่วนเกี่ยวข้องกับ C ++ แม้ว่ารหัสระดับสูงของคุณจะไม่ออกจากแอปพลิเคชันของคุณ DIP ช่วยให้คุณสามารถเปลี่ยนรหัสได้เมื่อคุณเพิ่มหรือเปลี่ยนรหัสระดับที่ต่ำกว่า สิ่งนี้จะช่วยลดค่าใช้จ่ายในการบำรุงรักษาและผลที่ตามมาจากการเปลี่ยนแปลง กรมทรัพย์สินทางปัญญาเป็นแนวคิดระดับที่สูงขึ้น หากคุณไม่เข้าใจคุณจำเป็นต้องทำ googling มากขึ้น
Dirk Bester

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

1
คุณไม่สามารถมองเห็นแอปพลิเคชันย้อนกลับของ DIP ได้หรือไม่ นั่นคือโมดูลระดับสูงใช้โปรแกรมของคุณเป็นหลัก ข้อกังวลหลักของคุณคือไม่ต้องนำมาใช้ซ้ำเพื่อให้น้อยขึ้นอยู่กับการใช้งานของโมดูลระดับล่างเพื่อให้การอัปเดตเพื่อให้ทันกับการเปลี่ยนแปลงในอนาคตจะแยกแอปพลิเคชันของคุณออกจากเวลาและเทคโนโลยีใหม่ ไม่ใช่เพื่อให้ง่ายต่อการแทนที่ WindowsOS แต่จะทำให้ WindowsOS น้อยลงขึ้นอยู่กับรายละเอียดการใช้งาน FAT / HDD เพื่อให้ NTFS / SSD รุ่นใหม่สามารถเสียบเข้ากับ WindowsOS ได้โดยไม่มีผลกระทบใด ๆ
knockNrod

1
หน้าจอ UI ไม่ใช่โมดูลระดับสูงสุดหรือไม่ควรใช้กับแอปพลิเคชันใด ๆ โมดูลระดับสูงสุดคือโมดูลที่มีกฎเกณฑ์ทางธุรกิจ โมดูลระดับสูงสุดไม่ได้ถูกกำหนดโดยศักยภาพในการใช้ซ้ำได้เช่นกัน โปรดตรวจสอบคำอธิบายง่ายๆของลุงบ๊อบในบทความนี้: blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba

8

โดยทั่วไปมันบอกว่า:

คลาสควรขึ้นอยู่กับ abstractions (เช่นอินเตอร์เฟส, คลาส abstract), ไม่ใช่รายละเอียดเฉพาะ (การประยุกต์ใช้)


มันง่ายอย่างนั้นเหรอ? มีบทความยาว ๆ และหนังสือหลายเล่มตามที่ DerekGreer พูดถึง? จริง ๆ แล้วฉันกำลังมองหาคำตอบง่าย ๆ แต่ไม่น่าเชื่อถ้ามันเป็นแบบนั้นง่าย: D
Darius.V

1
@ darius-v ไม่ใช่รุ่นอื่นที่พูดว่า "ใช้ abstractions" มันเกี่ยวกับผู้ที่เป็นเจ้าของอินเทอร์เฟซ อาจารย์ใหญ่กล่าวว่าไคลเอนต์ (ส่วนประกอบระดับสูงกว่า) ควรกำหนดอินเทอร์เฟซและส่วนประกอบระดับล่างควรใช้พวกเขา
boran

6

คำตอบที่ดีและแบบอย่างที่ดีได้รับจากผู้อื่นที่นี่แล้ว

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

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

คุณต้องการให้วัตถุ 'ระดับบนสุด' มีความเสถียรและไม่เปราะบางสำหรับการเปลี่ยนแปลงดังนั้นคุณต้องสลับการพึ่งพา


6
DIP ทำเช่นนั้นอย่างไรสำหรับรหัส Java หรือ. NET ในภาษาเหล่านั้นตรงกันข้ามกับ C ++ การเปลี่ยนแปลงการใช้งานโมดูลระดับต่ำไม่ต้องการการเปลี่ยนแปลงในโมดูลระดับสูงที่ใช้งาน เฉพาะการเปลี่ยนแปลงในส่วนต่อประสานสาธารณะเท่านั้นที่จะทำให้เกิดการกระเพื่อมผ่าน แต่สิ่งที่เป็นนามธรรมที่กำหนดไว้ในระดับที่สูงขึ้นจะต้องเปลี่ยนด้วย
Rogério

5

วิธีที่ชัดเจนกว่ามากในการระบุหลักการผกผันของการพึ่งพาคือ:

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

เช่นแทนที่จะใช้ชั้นเรียนของคุณLogicเป็นคนมักจะทำ:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

คุณควรทำสิ่งที่ชอบ:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

DataและDataFromDependencyควรอยู่ในโมดูลเดียวกับที่ไม่ได้กับLogicDependency

ทำไมต้องทำเช่นนี้?

  1. โมดูลตรรกะทางธุรกิจสองโมดูลแยกกันแล้ว เมื่อเปลี่ยนแปลงคุณไม่จำเป็นต้องมีการเปลี่ยนแปลงDependencyLogic
  2. ทำความเข้าใจกับสิ่งที่Logicเป็นงานที่ง่ายกว่ามาก: มันทำงานเฉพาะกับสิ่งที่ดูเหมือนว่า ADT
  3. Logicสามารถทดสอบได้ง่ายขึ้น ตอนนี้คุณสามารถสร้างอินสแตนซ์Dataของข้อมูลปลอมได้โดยตรงและส่งต่อโดยไม่จำเป็นต้องใช้ mocks หรือการทดสอบนั่งร้านที่ซับซ้อน

ฉันไม่คิดว่ามันถูกต้อง หากDataFromDependencyการอ้างอิงโดยตรงDependencyนั้นอยู่ในโมดูลเดียวกันกับโมดูลLogicนั้นก็Logicยังคงขึ้นอยู่กับDependencyโมดูลในเวลารวบรวม ตามคำอธิบายของลุงบ็อบเกี่ยวกับหลักการหลีกเลี่ยงนั่นคือประเด็นทั้งหมด แต่จะปฏิบัติตามกรมฯDataควรจะอยู่ในโมดูลเช่นเดียวLogicแต่ควรจะอยู่ในโมดูลเดียวกับDataFromDependency Dependency
Mark Amery

3

การผกผันของการควบคุม (IoC) เป็นรูปแบบการออกแบบที่วัตถุได้รับการพึ่งพาโดยกรอบนอกแทนที่จะขอกรอบสำหรับการพึ่งพา

ตัวอย่าง Pseudocode โดยใช้การค้นหาแบบดั้งเดิม:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

รหัสที่คล้ายกันโดยใช้ IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

ประโยชน์ของ IoC คือ:

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

หลักการผกผันของการพึ่งพาและการผกผันของการควบคุมเป็นสิ่งเดียวกันหรือไม่?
Peter Mortensen

3
"inversion" ใน DIP ไม่เหมือนกับใน Inversion of Control คนแรกเป็นเรื่องการรวบรวมเวลารวบรวมในขณะที่คนที่สองเป็นเรื่องของการควบคุมระหว่างวิธีการต่าง ๆ ที่รันไทม์
Rogério

ฉันรู้สึกว่ารุ่น IoC หายไปบางอย่าง วัตถุฐานข้อมูลได้รับการกำหนดอย่างไรมันมาจากไหน ฉันพยายามที่จะเข้าใจว่าสิ่งนี้ไม่เพียง แต่ล่าช้าระบุการพึ่งพาทั้งหมดไปยังระดับสูงสุดในการพึ่งพาอาศัยของพระเจ้ายักษ์
Richard Tingle

1
ดังที่ได้กล่าวแล้วโดย @ Rogérioกรมทรัพย์สินทางปัญญาไม่ได้เป็น DI / IoC คำตอบนี้ผิด IMO
zeraDev

1

จุดประสงค์ของการพึ่งพาอาศัยกันคือการสร้างซอฟต์แวร์ที่นำมาใช้ซ้ำได้

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

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

ลองจินตนาการรหัสเทียมนี้ ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass ขึ้นอยู่กับทั้งคลาส Service และ ServiceLocator โดยตรง มันต้องการทั้งสองอย่างถ้าคุณต้องการใช้ในแอปพลิเคชันอื่น ทีนี้ลองนึกภาพนี้ ...

public class MyClass
{
  public IService myService;
}

ตอนนี้ MyClass อาศัยอินเทอร์เฟซเดียวนั่นคืออินเตอร์เฟส IService เราอยากให้คอนเทนเนอร์ IoC ตั้งค่าตัวแปรนั้นจริง

ดังนั้นตอนนี้ MyClass สามารถนำกลับมาใช้ใหม่ได้อย่างง่ายดายในโครงการอื่น ๆ โดยไม่ต้องอาศัยการพึ่งพาของสองคลาสอื่น ๆ ร่วมกับมัน

ยิ่งไปกว่านั้นคุณไม่จำเป็นต้องลากการพึ่งพาของ MyService และการพึ่งพาของการพึ่งพาเหล่านั้นและ ... คุณจะได้รับแนวคิด


2
คำตอบนี้ไม่ได้เกี่ยวกับกรมทรัพย์สินทางปัญญา แต่อย่างอื่น โค้ดสองชิ้นสามารถพึ่งพาอินเตอร์เฟสที่เป็นนามธรรมและไม่ได้อยู่ในกันและกันและยังไม่เป็นไปตามหลักการการพึ่งพาการพึ่งพา
Rogério

1

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

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

"ระดับสูง" ในหลักการการผกผันของการพึ่งพาอาศัยหมายถึง "สำคัญกว่า"


1
นี่เป็นการเปรียบเทียบที่ดีอย่างน่าทึ่ง เท่าที่ภายใต้ DIC ส่วนประกอบระดับสูงยังคงพึ่งพารันไทม์บนชิ้นส่วนระดับต่ำในการเปรียบเทียบนี้การดำเนินการตามแผนระดับสูงขึ้นอยู่กับแผนระดับต่ำ แต่เช่นเดียวกับภายใต้ DIC มันเป็นแผนระดับต่ำที่ขึ้นอยู่กับเวลาในการรวบรวมแผนระดับสูงในกรณีที่คุณเปรียบเทียบว่าการก่อตัวของแผนระดับต่ำนั้นขึ้นอยู่กับแผนระดับสูง
Mark Amery

0

ฉันสามารถดูคำอธิบายที่ดีได้รับในคำตอบข้างต้น อย่างไรก็ตามฉันต้องการให้คำอธิบายง่ายๆด้วยตัวอย่างง่ายๆ

หลักการผกผันของการพึ่งพาอาศัยช่วยให้โปรแกรมเมอร์สามารถลบการอ้างอิงฮาร์ดโค้ดเพื่อให้แอปพลิเคชันกลายเป็นคู่ขนานและขยายได้อย่างอิสระ

ทำอย่างไรจึงจะได้สิ่งนี้:ผ่านสิ่งที่เป็นนามธรรม

โดยไม่ต้องพึ่งพาการผกผัน:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

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

ด้วยการผกผันของการพึ่งพา:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

-1

การพึ่งพาอาศัยการผกผัน: ขึ้นอยู่กับ abstractions ไม่ใช่ concretions

การผกผันของการควบคุม: Main vs Abstraction และ Main เป็นกาวของระบบอย่างไร

กรมทรัพย์สินทางปัญญาและ IoC

เหล่านี้เป็นโพสต์ที่ดีพูดคุยเกี่ยวกับเรื่องนี้:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/


-1

สมมติว่าเรามีสองชั้น: EngineerและProgrammer:

วิศวกรคลาสมีการพึ่งพาคลาสโปรแกรมเมอร์ดังต่อไปนี้:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

ในตัวอย่างชั้นEngineerนี้มีการพึ่งพาProgrammerชั้นเรียนของเรา จะเกิดอะไรขึ้นถ้าฉันต้องเปลี่ยนProgrammer?

เห็นได้ชัดว่าฉันต้องเปลี่ยนEngineerด้วย (ว้าว ณ จุดนี้OCPมีการละเมิดด้วย)

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

หมายเหตุ: DependencyInjectionสามารถช่วยให้เราทำDIPและSRPมากเกินไป


-1

การเพิ่มคำตอบที่ดีโดยทั่วไปให้เพิ่มขึ้นฉันต้องการเพิ่มตัวอย่างเล็ก ๆ ของตัวเองเพื่อแสดงการปฏิบัติที่ดีและไม่ดี และใช่ฉันไม่ใช่คนขว้างก้อนหิน!

สมมติว่าคุณต้องการโปรแกรมเล็ก ๆ เพื่อแปลงสตริงเป็นรูปแบบ base64ผ่าน console I / O นี่คือวิธีการที่ไร้เดียงสา:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

โดยทั่วไปแล้วกรมทรัพย์สินทางปัญญากล่าวว่าส่วนประกอบระดับสูงไม่ควรขึ้นอยู่กับการนำไปใช้ในระดับต่ำโดยที่ "ระดับ" คือระยะทางจาก I / O ตาม Robert C. Martin ("สถาปัตยกรรมสะอาด") แต่คุณจะออกจากสถานการณ์นี้ได้อย่างไร เพียงแค่ทำให้ตัวเข้ารหัสส่วนกลางขึ้นอยู่กับส่วนต่อประสานโดยไม่ต้องสนใจว่าจะใช้งานอย่างไร:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

โปรดทราบว่าคุณไม่จำเป็นต้องสัมผัสGoodEncoderเพื่อเปลี่ยนโหมด I / O - คลาสนั้นพอใจกับอินเตอร์เฟส I / O ที่รู้ การใช้งานในระดับต่ำIReadableและIWriteableจะไม่รบกวน


ฉันไม่คิดว่านี่คือการพึ่งพิงการพึ่งพา ท้ายที่สุดคุณไม่ได้คว่ำการพึ่งพาใด ๆ ที่นี่; ผู้อ่านและนักเขียนของคุณไม่ได้ขึ้นอยู่กับGoodEncoderตัวอย่างที่สองของคุณ ในการสร้างตัวอย่าง DIP คุณจะต้องแนะนำแนวคิดเกี่ยวกับสิ่งที่ "เป็นเจ้าของ" อินเทอร์เฟซที่คุณแยกมาที่นี่ - และโดยเฉพาะอย่างยิ่งเพื่อวางไว้ในแพ็คเกจเดียวกันกับ GoodEncoder ในขณะที่การนำไปใช้ยังคงอยู่ภายนอก
Mark Amery

-2

หลักการผกผันอ้างอิง (DIP) กล่าวว่า

i) โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองควรขึ้นอยู่กับ abstractions

ii) Abstractions ไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียดควรขึ้นอยู่กับ abstractions

ตัวอย่าง:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

หมายเหตุ: คลาสควรขึ้นอยู่กับ abstractions เช่นอินเตอร์เฟสหรือคลาส abstract ไม่ใช่รายละเอียดเฉพาะ (การใช้อินเตอร์เฟส)

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