หลักการผกผันของการพึ่งพาคืออะไรและเหตุใดจึงสำคัญ?
หลักการผกผันของการพึ่งพาคืออะไรและเหตุใดจึงสำคัญ?
คำตอบ:
ตรวจสอบเอกสารนี้ออก: พึ่งพาผกผันหลักการ
โดยพื้นฐานแล้วมันพูดว่า:
สำหรับเหตุผลที่สำคัญในระยะสั้น: การเปลี่ยนแปลงมีความเสี่ยงและโดยขึ้นอยู่กับแนวคิดแทนที่จะเป็นการนำไปใช้งานคุณลดความจำเป็นในการเปลี่ยนแปลงที่ไซต์การโทร
ได้อย่างมีประสิทธิภาพกรมทรัพย์สินทางปัญญาลดการมีเพศสัมพันธ์ระหว่างชิ้นส่วนของรหัสที่แตกต่างกัน แนวคิดคือแม้ว่าจะมีหลายวิธีในการนำไปใช้พูดสิ่งอำนวยความสะดวกการเข้าสู่ระบบวิธีที่คุณจะใช้มันควรจะค่อนข้างคงที่ในเวลา หากคุณสามารถแยกอินเทอร์เฟซที่แสดงถึงแนวคิดของการบันทึกอินเทอร์เฟซนี้ควรจะมีเสถียรภาพมากขึ้นในเวลากว่าการใช้งานและไซต์โทรควรได้รับผลกระทบน้อยกว่ามากจากการเปลี่ยนแปลงที่คุณสามารถทำได้ในขณะที่รักษาหรือขยายกลไกการบันทึก
ด้วยการทำให้การใช้งานขึ้นอยู่กับส่วนต่อประสานคุณจะได้รับความเป็นไปได้ในการเลือกในขณะใช้งานซึ่งการปรับใช้นั้นเหมาะสมกับสภาพแวดล้อมของคุณ สิ่งนี้อาจน่าสนใจเช่นกัน
หนังสือการพัฒนาซอฟต์แวร์ Agile หลักการรูปแบบและการปฏิบัติและหลักการ Agile รูปแบบและการปฏิบัติใน C # เป็นแหล่งข้อมูลที่ดีที่สุดสำหรับการทำความเข้าใจเป้าหมายและแรงจูงใจที่แท้จริงภายใต้หลักการผกผันของการพึ่งพา บทความ "หลักการพึ่งพาการพึ่งพาอาศัยกัน" เป็นทรัพยากรที่ดี แต่เนื่องจากข้อเท็จจริงที่ว่ามันเป็นฉบับย่อของร่างซึ่งในที่สุดก็หาทางเข้าไปในหนังสือที่กล่าวถึงก่อนหน้านี้มันทำให้การอภิปรายที่สำคัญบางอย่างเกี่ยวกับแนวคิดของ ความเป็นเจ้าของบรรจุภัณฑ์และส่วนต่อประสานซึ่งเป็นกุญแจสำคัญในการแยกความแตกต่างของหลักการนี้จากคำแนะนำทั่วไปมากขึ้นถึง "โปรแกรมไปยังส่วนต่อประสานกับผู้อื่นไม่ใช่การนำไปปฏิบัติ" ที่พบในหนังสือ
เพื่อให้ข้อมูลสรุปที่พึ่งพาผกผันหลักการเป็นหลักเกี่ยวกับการย้อนกลับทิศทางการทั่วไปของการอ้างอิงจาก "ระดับที่สูงขึ้น" ส่วนที่ "ระดับที่ต่ำกว่า" องค์ประกอบดังกล่าวว่า "ระดับที่ต่ำกว่า" ส่วนจะขึ้นอยู่กับอินเตอร์เฟซที่เป็นเจ้าของโดย "ระดับที่สูงขึ้น" ส่วนประกอบ . (หมายเหตุ: ส่วนประกอบ "ระดับที่สูงกว่า" ที่นี่หมายถึงส่วนประกอบที่ต้องการการพึ่งพา / บริการภายนอกไม่จำเป็นต้องมีตำแหน่งที่เป็นแนวคิดในสถาปัตยกรรมแบบเลเยอร์) ในการทำเช่นนี้การมีเพศสัมพันธ์ไม่ได้ลดลงมากเท่าที่เปลี่ยนจากส่วนประกอบที่มีเหตุผล มีค่าน้อยลงสำหรับส่วนประกอบที่มีคุณค่าทางทฤษฎีมากกว่า
นี่คือความสำเร็จโดยการออกแบบส่วนประกอบที่มีการพึ่งพาภายนอกจะแสดงในแง่ของอินเทอร์เฟซที่การดำเนินการจะต้องให้บริการโดยผู้บริโภคขององค์ประกอบ กล่าวอีกนัยหนึ่งอินเทอร์เฟซที่กำหนดแสดงสิ่งที่จำเป็นโดยคอมโพเนนต์ไม่ใช่วิธีที่คุณใช้คอมโพเนนต์ (เช่น "INeedSomething" ไม่ใช่ "IDoSomething")
สิ่งที่หลักการการพึ่งพาการพึ่งพาไม่ได้อ้างถึงคือการฝึกฝนอย่างง่ายของการพึ่งพาแบบนามธรรมผ่านการใช้อินเตอร์เฟส (เช่น MyService → [ILogger ⇐ Logger]) แม้ว่าสิ่งนี้จะแยกส่วนประกอบจากรายละเอียดการใช้งานเฉพาะของการพึ่งพา แต่ก็ไม่ได้กลับความสัมพันธ์ระหว่างผู้บริโภคและการพึ่งพา (เช่น [MyService → IMyServiceLogger] ⇐ Logger
ความสำคัญของหลักการการพึ่งพาการพึ่งพาสามารถกลั่นลงไปสู่เป้าหมายที่เป็นเอกเทศในการใช้ซ้ำส่วนประกอบซอฟต์แวร์ที่พึ่งพาการพึ่งพาจากภายนอกสำหรับส่วนหนึ่งของการทำงาน (การบันทึกการตรวจสอบและอื่น ๆ )
ภายในเป้าหมายทั่วไปของการใช้ซ้ำเราสามารถแยกประเภทการใช้ซ้ำสองประเภท:
การใช้ส่วนประกอบซอฟต์แวร์ภายในแอพพลิเคชั่นหลายตัวพร้อมกับการนำไปใช้งานแบบ sub-dependency (เช่นคุณได้พัฒนา DI container และต้องการจัดทำบันทึกข้อมูล แต่ไม่ต้องการเชื่อมโยงคอนเทนเนอร์ของคุณเข้ากับตัวบันทึกเฉพาะเช่นทุกคนที่ใช้คอนเทนเนอร์ของคุณ ใช้ไลบรารีการบันทึกที่คุณเลือก)
การใช้ส่วนประกอบซอฟต์แวร์ภายในบริบทที่มีการพัฒนา (เช่นคุณได้พัฒนาส่วนประกอบทางธุรกิจเชิงตรรกะซึ่งยังคงเหมือนเดิมในแอพพลิเคชั่นหลายรุ่นที่มีการพัฒนารายละเอียดการใช้งาน)
ด้วยกรณีแรกของการนำองค์ประกอบมาใช้ซ้ำในแอพพลิเคชั่นหลายตัวเช่นไลบรารีโครงสร้างพื้นฐานเป้าหมายคือการจัดหาโครงสร้างพื้นฐานหลักที่จำเป็นสำหรับผู้บริโภคของคุณโดยไม่ต้องเชื่อมโยงผู้บริโภคของคุณกับการพึ่งพาย่อยของห้องสมุดของคุณเอง ผู้บริโภคต้องการการพึ่งพาเหมือนกันเช่นกัน นี่อาจเป็นปัญหาเมื่อผู้บริโภคห้องสมุดของคุณเลือกที่จะใช้ห้องสมุดที่แตกต่างกันสำหรับความต้องการโครงสร้างพื้นฐานเดียวกัน (เช่น NLog vs. log4net) หรือหากพวกเขาเลือกที่จะใช้ไลบรารีที่ต้องการรุ่นที่ใหม่กว่าซึ่งไม่สามารถใช้งานร่วมกับรุ่นหลังได้ ที่ห้องสมุดของคุณต้องการ
ด้วยกรณีที่สองของการนำองค์ประกอบทางธุรกิจมาใช้ใหม่ (เช่น "ส่วนประกอบระดับสูง") เป้าหมายคือเพื่อแยกการใช้งานโดเมนหลักของแอปพลิเคชันของคุณจากความต้องการที่เปลี่ยนแปลงของรายละเอียดการใช้งานของคุณ (เช่นการเปลี่ยน / อัปเกรด กลยุทธ์การเข้ารหัส ฯลฯ ) เป็นการดีที่การเปลี่ยนแปลงรายละเอียดการใช้งานของแอปพลิเคชันไม่ควรทำลายส่วนประกอบที่ห่อหุ้มตรรกะทางธุรกิจของแอปพลิเคชัน
หมายเหตุ: บางคนอาจคัดค้านการอธิบายกรณีที่สองนี้ว่าเป็นการใช้ซ้ำจริงโดยให้เหตุผลว่าส่วนประกอบเช่นส่วนประกอบทางธุรกิจเชิงตรรกะที่ใช้ภายในแอพพลิเคชั่นที่พัฒนาขึ้นครั้งเดียวแสดงถึงการใช้เพียงครั้งเดียว อย่างไรก็ตามความคิดในที่นี้คือการเปลี่ยนแปลงรายละเอียดการใช้งานของแอปพลิเคชันแต่ละครั้งทำให้เกิดบริบทใหม่และดังนั้นจึงเป็นกรณีการใช้งานที่แตกต่างกันแม้ว่าเป้าหมายสูงสุดอาจแตกต่างได้เมื่อเทียบกับการพกพา
ในขณะที่ปฏิบัติตามหลักการการพึ่งพาการพึ่งพาในกรณีที่สองนี้สามารถให้ประโยชน์บางอย่าง แต่ก็ควรสังเกตว่าคุณค่าของมันที่นำไปใช้กับภาษาสมัยใหม่เช่น Java และ C # จะลดลงมากอาจจะเป็นประเด็นที่ไม่เกี่ยวข้อง ดังที่ได้กล่าวไว้ก่อนหน้านี้กรมฯ จะทำการแยกรายละเอียดการปฏิบัติออกเป็นแพ็คเกจแยกต่างหากอย่างสมบูรณ์ ในกรณีของแอพพลิเคชั่นที่มีการพัฒนาอย่างไรก็ตามการใช้อินเตอร์เฟสที่กำหนดไว้ในแง่ของโดเมนธุรกิจจะช่วยป้องกันไม่ให้จำเป็นต้องแก้ไขส่วนประกอบระดับสูงเนื่องจากความต้องการการเปลี่ยนแปลงของส่วนประกอบรายละเอียดการใช้งานที่เปลี่ยนแปลงไป . หลักการส่วนนี้สะท้อนถึงแง่มุมที่เกี่ยวข้องกับภาษาในมุมมองเมื่อหลักการประมวลผล (เช่น C ++) ซึ่งไม่เกี่ยวข้องกับภาษาที่ใหม่กว่า ที่กล่าวว่า
การอภิปรายนานของหลักการนี้เป็นที่เกี่ยวกับการใช้งานที่เรียบง่ายของอินเตอร์เฟซการพึ่งพาการฉีดและรูปแบบการเชื่อมต่อแยกกันสามารถพบได้ที่นี่ ภาษานอกจากนี้การอภิปรายของวิธีการหลักการที่เกี่ยวข้องกับการพิมพ์แบบไดนามิกเช่น JavaScript สามารถพบได้ที่นี่
เมื่อเราออกแบบแอปพลิเคชั่นซอฟต์แวร์เราสามารถพิจารณาคลาสระดับต่ำได้คลาสที่ใช้การดำเนินการขั้นพื้นฐานและหลัก (การเข้าถึงดิสก์, โปรโตคอลเครือข่าย, ... ) และคลาสระดับสูงซึ่งเป็นคลาสที่ห่อหุ้มตรรกะที่ซับซ้อน (กระแสธุรกิจ ... )
อันสุดท้ายพึ่งพาคลาสระดับต่ำ วิธีธรรมชาติของการใช้โครงสร้างดังกล่าวคือการเขียนคลาสระดับต่ำและเมื่อเราให้พวกเขาเขียนคลาสระดับสูงที่ซับซ้อน เนื่องจากชั้นเรียนระดับสูงมีการกำหนดในแง่ของคนอื่น ๆ นี้ดูเหมือนว่าวิธีตรรกะที่จะทำ แต่นี่ไม่ใช่การออกแบบที่ยืดหยุ่น จะเกิดอะไรขึ้นถ้าเราจำเป็นต้องเปลี่ยนชั้นเรียนระดับต่ำ
หลักการผกผันของการพึ่งพานั้นระบุว่า:
หลักการนี้พยายามที่จะ "กลับ" แนวคิดดั้งเดิมที่โมดูลระดับสูงในซอฟต์แวร์ควรขึ้นอยู่กับโมดูลระดับล่าง ที่นี่โมดูลระดับสูงเป็นเจ้าของสิ่งที่เป็นนามธรรม (ตัวอย่างเช่นการตัดสินใจวิธีการของอินเตอร์เฟส) ซึ่งนำมาใช้โดยโมดูลระดับล่าง ดังนั้นการทำให้โมดูลระดับล่างขึ้นอยู่กับโมดูลระดับที่สูงขึ้น
การผกผันของการพึ่งพาที่ใช้กันอย่างดีนั้นให้ความยืดหยุ่นและความมั่นคงในระดับสถาปัตยกรรมทั้งหมดของแอปพลิเคชันของคุณ มันจะช่วยให้แอปพลิเคชันของคุณพัฒนาอย่างมั่นคงและปลอดภัยยิ่งขึ้น
ตามเนื้อผ้า 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 )
มันเกิดขึ้นในลักษณะเดียวกันกับสถาปัตยกรรมหกเหลี่ยมซึ่งโดเมนตั้งอยู่ในภาคกลางและพอร์ตต่างก็เป็นนามธรรมของการสื่อสารจากโดมิโนภายนอก ที่นี่อีกครั้งเห็นได้ชัดว่าโดเมนที่มีเสถียรภาพมากที่สุดและการพึ่งพาแบบดั้งเดิมจะคว่ำ
สำหรับฉันหลักการพึ่งพาการพึ่งพา (Dependency Inversion Principle) ดังที่อธิบายไว้ในบทความอย่างเป็นทางการเป็นความพยายามที่ผิดพลาดจริง ๆ ในการเพิ่มความสามารถในการนำโมดูลกลับมาใช้ซ้ำซึ่งนำมาใช้ซ้ำได้น้อยลง
ปัญหาใน C ++ คือไฟล์ส่วนหัวนั้นมักจะมีการประกาศของเขตข้อมูลส่วนตัวและวิธีการ ดังนั้นถ้าโมดูล C ++ ระดับสูงมีไฟล์ส่วนหัวสำหรับโมดูลระดับต่ำมันจะขึ้นอยู่กับรายละเอียดการใช้งานจริงของโมดูลนั้น และแน่นอนว่าไม่ใช่สิ่งที่ดี แต่นี่ไม่ใช่ปัญหาในภาษาที่ทันสมัยกว่าที่ใช้กันทั่วไปในปัจจุบัน
โมดูลระดับสูงนั้นสามารถนำมาใช้ซ้ำได้น้อยกว่าโมดูลระดับต่ำเนื่องจากโดยปกติแล้วแอปพลิเคชัน / บริบทจะมีความเฉพาะเจาะจงมากกว่าโมดูลหลัง ตัวอย่างเช่นองค์ประกอบที่ใช้หน้าจอ UI เป็นระดับสูงสุดและเฉพาะเจาะจงมากสำหรับแอปพลิเคชัน การพยายามนำองค์ประกอบดังกล่าวกลับมาใช้ซ้ำในแอปพลิเคชันอื่นนั้นมีประสิทธิภาพในการต่อต้านและสามารถนำไปสู่การทำวิศวกรรมมากเกินไปได้
ดังนั้นการสร้างสิ่งที่เป็นนามธรรมแยกในระดับเดียวกันขององค์ประกอบที่ขึ้นอยู่กับองค์ประกอบ B (ซึ่งไม่ได้ขึ้นอยู่กับ A) สามารถทำได้เฉพาะในกรณีที่องค์ประกอบ A จะเป็นประโยชน์จริงๆสำหรับการใช้งานในบริบทที่แตกต่างกัน หากไม่ใช่ในกรณีนั้นการใช้งาน DIP จะเป็นการออกแบบที่ไม่ดี
โดยทั่วไปมันบอกว่า:
คลาสควรขึ้นอยู่กับ abstractions (เช่นอินเตอร์เฟส, คลาส abstract), ไม่ใช่รายละเอียดเฉพาะ (การประยุกต์ใช้)
คำตอบที่ดีและแบบอย่างที่ดีได้รับจากผู้อื่นที่นี่แล้ว
เหตุผลที่กรมทรัพย์สินทางปัญญามีความสำคัญก็เพราะมันทำให้มั่นใจได้ในหลักการ OO "การออกแบบคู่ที่หลวม"
วัตถุในซอฟต์แวร์ของคุณไม่ควรเข้าสู่ลำดับชั้นที่บางวัตถุเป็นวัตถุระดับบนสุดขึ้นอยู่กับวัตถุระดับต่ำ การเปลี่ยนแปลงในวัตถุระดับต่ำจะกระเพื่อมผ่านไปยังวัตถุระดับบนสุดของคุณซึ่งทำให้ซอฟต์แวร์เปราะบางมากสำหรับการเปลี่ยนแปลง
คุณต้องการให้วัตถุ 'ระดับบนสุด' มีความเสถียรและไม่เปราะบางสำหรับการเปลี่ยนแปลงดังนั้นคุณต้องสลับการพึ่งพา
วิธีที่ชัดเจนกว่ามากในการระบุหลักการผกผันของการพึ่งพาคือ:
โมดูลของคุณที่มีแค็ปซูลลอจิกทางธุรกิจที่ซับซ้อนไม่ควรขึ้นอยู่กับโมดูลอื่นที่แค็ปซูลตรรกะทางธุรกิจโดยตรง แต่พวกเขาควรพึ่งพาอินเตอร์เฟสกับข้อมูลอย่างง่ายเท่านั้น
เช่นแทนที่จะใช้ชั้นเรียนของคุณ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
ควรอยู่ในโมดูลเดียวกับที่ไม่ได้กับLogic
Dependency
ทำไมต้องทำเช่นนี้?
Dependency
Logic
Logic
เป็นงานที่ง่ายกว่ามาก: มันทำงานเฉพาะกับสิ่งที่ดูเหมือนว่า ADTLogic
สามารถทดสอบได้ง่ายขึ้น ตอนนี้คุณสามารถสร้างอินสแตนซ์Data
ของข้อมูลปลอมได้โดยตรงและส่งต่อโดยไม่จำเป็นต้องใช้ mocks หรือการทดสอบนั่งร้านที่ซับซ้อนDataFromDependency
การอ้างอิงโดยตรงDependency
นั้นอยู่ในโมดูลเดียวกันกับโมดูลLogic
นั้นก็Logic
ยังคงขึ้นอยู่กับDependency
โมดูลในเวลารวบรวม ตามคำอธิบายของลุงบ็อบเกี่ยวกับหลักการหลีกเลี่ยงนั่นคือประเด็นทั้งหมด แต่จะปฏิบัติตามกรมฯData
ควรจะอยู่ในโมดูลเช่นเดียวLogic
แต่ควรจะอยู่ในโมดูลเดียวกับDataFromDependency
Dependency
การผกผันของการควบคุม (IoC) เป็นรูปแบบการออกแบบที่วัตถุได้รับการพึ่งพาโดยกรอบนอกแทนที่จะขอกรอบสำหรับการพึ่งพา
ตัวอย่าง Pseudocode โดยใช้การค้นหาแบบดั้งเดิม:
class Service {
Database database;
init() {
database = FrameworkSingleton.getService("database");
}
}
รหัสที่คล้ายกันโดยใช้ IoC:
class Service {
Database database;
init(database) {
this.database = database;
}
}
ประโยชน์ของ IoC คือ:
จุดประสงค์ของการพึ่งพาอาศัยกันคือการสร้างซอฟต์แวร์ที่นำมาใช้ซ้ำได้
แนวคิดก็คือแทนที่จะใช้โค้ดสองชิ้นที่พึ่งพาซึ่งกันและกัน จากนั้นคุณสามารถนำชิ้นส่วนทั้งสองกลับมาใช้โดยไม่ใช้ชิ้นส่วนอื่น
วิธีการนี้ประสบความสำเร็จมากที่สุดคือผ่านคอนเทนเนอร์ผกผันของการควบคุม (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 และการพึ่งพาของการพึ่งพาเหล่านั้นและ ... คุณจะได้รับแนวคิด
หากเราสามารถรับเป็นพนักงานระดับสูงใน บริษัท ได้รับค่าตอบแทนสำหรับการดำเนินการตามแผนของพวกเขาและแผนการเหล่านี้จะถูกส่งมอบโดยการดำเนินการโดยรวมของแผนพนักงานของ "ระดับต่ำ" มากมายเราสามารถพูดได้ โดยทั่วไปแล้วมันเป็นแผนแย่มากหากคำอธิบายแผนของพนักงานระดับสูงในทางใดทางหนึ่งควบคู่ไปกับแผนเฉพาะของพนักงานระดับล่าง
หากผู้บริหารระดับสูงมีแผนที่จะ "ปรับปรุงเวลาการส่งมอบ" และระบุว่าพนักงานในสายการเดินเรือจะต้องมีกาแฟและเหยียดทุกเช้าแผนนั้นจะมีความสอดคล้องสูงและมีการทำงานร่วมกันต่ำ แต่ถ้าแผนไม่ได้เอ่ยถึงพนักงานที่เฉพาะเจาะจงใด ๆ และในความเป็นจริงก็ต้องการ "เอนทิตีที่สามารถทำงานได้พร้อมที่จะทำงาน" จากนั้นแผนจะถูกรวมเข้าด้วยกันและแน่นหนามากขึ้น: แผนไม่ทับซ้อนกันและสามารถทดแทนได้ง่าย . ผู้รับเหมาหรือหุ่นยนต์สามารถเปลี่ยนพนักงานได้อย่างง่ายดายและแผนระดับสูงยังคงไม่เปลี่ยนแปลง
"ระดับสูง" ในหลักการการผกผันของการพึ่งพาอาศัยหมายถึง "สำคัญกว่า"
ฉันสามารถดูคำอธิบายที่ดีได้รับในคำตอบข้างต้น อย่างไรก็ตามฉันต้องการให้คำอธิบายง่ายๆด้วยตัวอย่างง่ายๆ
หลักการผกผันของการพึ่งพาอาศัยช่วยให้โปรแกรมเมอร์สามารถลบการอ้างอิงฮาร์ดโค้ดเพื่อให้แอปพลิเคชันกลายเป็นคู่ขนานและขยายได้อย่างอิสระ
ทำอย่างไรจึงจะได้สิ่งนี้:ผ่านสิ่งที่เป็นนามธรรม
โดยไม่ต้องพึ่งพาการผกผัน:
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;
}
}
การพึ่งพาอาศัยการผกผัน: ขึ้นอยู่กับ abstractions ไม่ใช่ concretions
การผกผันของการควบคุม: Main vs Abstraction และ Main เป็นกาวของระบบอย่างไร
เหล่านี้เป็นโพสต์ที่ดีพูดคุยเกี่ยวกับเรื่องนี้:
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/
สมมติว่าเรามีสองชั้น: 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
มากเกินไป
การเพิ่มคำตอบที่ดีโดยทั่วไปให้เพิ่มขึ้นฉันต้องการเพิ่มตัวอย่างเล็ก ๆ ของตัวเองเพื่อแสดงการปฏิบัติที่ดีและไม่ดี และใช่ฉันไม่ใช่คนขว้างก้อนหิน!
สมมติว่าคุณต้องการโปรแกรมเล็ก ๆ เพื่อแปลงสตริงเป็นรูปแบบ 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 ในขณะที่การนำไปใช้ยังคงอยู่ภายนอก
หลักการผกผันอ้างอิง (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 ไม่ใช่รายละเอียดเฉพาะ (การใช้อินเตอร์เฟส)