ก่อนอื่นฉันต้องการอธิบายข้อสันนิษฐานที่ฉันทำกับคำตอบนี้ ไม่เป็นความจริงเสมอไป แต่มักจะ:
การเชื่อมต่อเป็นคำคุณศัพท์; ชั้นเรียนเป็นคำนาม
(อันที่จริงมีอินเทอร์เฟซที่เป็นคำนามเช่นกัน แต่ฉันต้องการพูดคุยที่นี่)
ดังนั้นเช่นอินเตอร์เฟซที่อาจจะเป็นสิ่งเช่นIDisposable
, หรือIEnumerable
IPrintable
ชั้นคือการดำเนินการที่เกิดขึ้นจริงของหนึ่งหรือมากกว่าของการเชื่อมต่อเหล่านี้: List
หรือทั้งอาจจะมีการใช้งานของMap
IEnumerable
ในการรับประเด็น: บ่อยครั้งที่คลาสของคุณต้องพึ่งพาซึ่งกันและกัน เช่นคุณอาจมีDatabase
คลาสที่เข้าถึงฐานข้อมูลของคุณ (hah, surprise! ;-)) แต่คุณต้องการให้คลาสนี้ทำการบันทึกเกี่ยวกับการเข้าถึงฐานข้อมูล สมมติว่าคุณมีชั้นอื่นLogger
แล้วมีการพึ่งพาไปDatabase
Logger
จนถึงตอนนี้ดีมาก
คุณสามารถสร้างแบบจำลองการอ้างอิงนี้ภายในDatabase
ชั้นเรียนของคุณด้วยบรรทัดต่อไปนี้:
var logger = new Logger();
และทุกอย่างดี เป็นเรื่องที่ดีเมื่อคุณรู้ว่าคุณต้องมีตัวบันทึกจำนวนมาก: บางครั้งคุณต้องการเข้าสู่คอนโซลบางครั้งไปยังระบบไฟล์บางครั้งใช้ TCP / IP และเซิร์ฟเวอร์การบันทึกระยะไกลและอื่น ๆ ...
และแน่นอนคุณไม่ต้องการเปลี่ยนรหัสทั้งหมดของคุณ (ในขณะที่คุณมี gazillions อยู่) และแทนที่ทุกบรรทัด
var logger = new Logger();
โดย:
var logger = new TcpLogger();
ครั้งแรกมันไม่สนุก ประการที่สองนี่คือข้อผิดพลาดได้ง่าย ประการที่สามนี่เป็นงานที่ทำซ้ำได้โง่สำหรับฝึกลิง แล้วคุณจะทำอย่างไร?
เห็นได้ชัดว่าเป็นความคิดที่ดีที่จะแนะนำอินเทอร์เฟซICanLog
(หรือคล้ายกัน) ที่ใช้งานโดยผู้บันทึกข้อมูลต่าง ๆ ทั้งหมด ดังนั้นขั้นตอนที่ 1 ในรหัสของคุณคือคุณ:
ICanLog logger = new Logger();
ตอนนี้การอนุมานประเภทจะไม่เปลี่ยนประเภทอีกต่อไปคุณมีหนึ่งอินเทอร์เฟซเดียวที่จะพัฒนา ขั้นตอนต่อไปคือคุณไม่ต้องการมีnew Logger()
ซ้ำแล้วซ้ำอีก ดังนั้นคุณจึงใส่ความน่าเชื่อถือในการสร้างอินสแตนซ์ใหม่ให้กับคลาสโรงงานส่วนกลางและคุณจะได้รับรหัสเช่น:
ICanLog logger = LoggerFactory.Create();
โรงงานเองตัดสินใจว่าจะสร้างตัวบันทึกประเภทใด รหัสของคุณไม่สนใจอีกต่อไปและหากคุณต้องการเปลี่ยนประเภทของเครื่องบันทึกที่ใช้งานคุณจะต้องเปลี่ยนหนึ่งครั้ง : ภายในโรงงาน
แน่นอนว่าคุณสามารถทำให้โรงงานนี้เป็นมาตรฐานและทำให้ใช้ได้กับทุกประเภท:
ICanLog logger = TypeFactory.Create<ICanLog>();
ที่ใดที่หนึ่ง TypeFactory นี้ต้องการข้อมูลการกำหนดว่าคลาสจริงจะสร้างอินสแตนซ์เมื่อมีการร้องขอประเภทอินเตอร์เฟสที่เฉพาะเจาะจงดังนั้นคุณต้องทำการแมป แน่นอนคุณสามารถทำแผนที่นี้ในรหัสของคุณ แต่แล้วการเปลี่ยนแปลงประเภทหมายถึงการคอมไพล์ใหม่ แต่คุณสามารถวางการแมปนี้ไว้ในไฟล์ XML ได้เช่น สิ่งนี้ช่วยให้คุณสามารถเปลี่ยนคลาสที่ใช้งานจริงแม้หลังจากรวบรวมเวลา (!) นั่นหมายถึงแบบไดนามิกโดยไม่ต้องคอมไพล์ใหม่!
ในการให้ตัวอย่างที่มีประโยชน์กับคุณ: คิดถึงซอฟต์แวร์ที่ไม่ได้เข้าสู่ระบบตามปกติ แต่เมื่อลูกค้าของคุณโทรเข้ามาและขอความช่วยเหลือเพราะเขามีปัญหาสิ่งที่คุณส่งให้เขาคือไฟล์ XML config ที่ได้รับการอัปเดต เปิดใช้งานการบันทึกและการสนับสนุนของคุณสามารถใช้ไฟล์บันทึกเพื่อช่วยลูกค้าของคุณ
และตอนนี้เมื่อคุณเปลี่ยนชื่อนิดหน่อยคุณก็จบลงด้วยการใช้งานตัวระบุบริการอย่างง่ายซึ่งเป็นหนึ่งในสองรูปแบบสำหรับInversion of Control (เนื่องจากคุณไม่สามารถควบคุมได้ว่าใครเป็นผู้ตัดสินใจว่าจะเรียนอินสแตนซ์อะไร)
ทั้งหมดนี้ช่วยลดการพึ่งพาในโค้ดของคุณ แต่ตอนนี้โค้ดทั้งหมดของคุณมีการอ้างอิงไปยังตัวระบุบริการส่วนกลางเพียงตัวเดียว
ตอนนี้การฉีดการพึ่งพาเป็นขั้นตอนต่อไปในบรรทัดนี้: เพียงแค่กำจัดการพึ่งพาตัวเดียวไปยัง locator service: แทนคลาสต่างๆที่ขอให้บริการ locator สำหรับการนำไปใช้งานสำหรับ interface เฉพาะคุณ - อีกครั้ง .
ด้วยการฉีดพึ่งพาDatabase
ชั้นเรียนของคุณตอนนี้มีนวกรรมิกที่ต้องใช้พารามิเตอร์ประเภทICanLog
:
public Database(ICanLog logger) { ... }
ตอนนี้ฐานข้อมูลของคุณมีตัวบันทึกที่จะใช้เสมอ แต่ก็ไม่ทราบว่าตัวบันทึกนี้มาจากไหน
และนี่คือที่ที่เฟรมเวิร์ก DI เข้ามาเล่น: คุณกำหนดค่าการแม็พของคุณอีกครั้งจากนั้นขอให้เฟรมเวิร์ก DI ของคุณอินสแตนซ์แอปพลิเคชันของคุณให้คุณ เนื่องจากApplication
คลาสต้องการICanPersistData
การนำไปใช้งานอินสแตนซ์ของDatabase
จะถูกฉีด - แต่จะต้องสร้างอินสแตนซ์ของชนิดของตัวบันทึกที่ถูกกำหนดค่าICanLog
ก่อน และอื่น ๆ ...
ดังนั้นเพื่อตัดเรื่องสั้นสั้น ๆ : การฉีดการพึ่งพาเป็นหนึ่งในสองวิธีของการลบการอ้างอิงในโค้ดของคุณ มันมีประโยชน์มากสำหรับการเปลี่ยนแปลงการกำหนดค่าหลังจากรวบรวมเวลาและเป็นสิ่งที่ยอดเยี่ยมสำหรับการทดสอบหน่วย (เนื่องจากทำให้ง่ายต่อการฉีดสตับและ / หรือ mocks)
ในทางปฏิบัติมีบางสิ่งที่คุณไม่สามารถทำได้โดยไม่มีตัวระบุตำแหน่งบริการ (เช่นหากคุณไม่ทราบล่วงหน้าว่ามีอินสแตนซ์ที่คุณต้องการอินเทอร์เฟซเฉพาะจำนวนเท่าไร: กรอบงาน DI จะแทรกหนึ่งอินสแตนซ์ต่อพารามิเตอร์หนึ่งตัวเท่านั้น เซอร์วิส locator ภายในลูป, แน่นอน), ดังนั้นส่วนใหญ่เฟรมเวิร์ก DI แต่ละตัวจึงจัดเตรียม service locator
แต่โดยทั่วไปนั่นแหละ
PS: สิ่งที่ฉันอธิบายไว้ที่นี่เป็นเทคนิคที่เรียกว่าการสร้างคอนสตรัคชันนอกจากนี้ยังมีการฉีดคุณสมบัติที่ไม่ใช่พารามิเตอร์คอนสตรัค แต่มีการใช้คุณสมบัติเพื่อกำหนดและแก้ไขการพึ่งพา คิดว่าการฉีดคุณสมบัติเป็นการพึ่งพาตัวเลือกและการฉีดคอนสตรัคเตอร์เป็นการอ้างอิงที่บังคับ แต่การอภิปรายในเรื่องนี้อยู่นอกเหนือขอบเขตของคำถามนี้