Inversion of Control là một nguyên tắc thiết kế chung của kiến trúc phần mềm hỗ trợ tạo ra các khung phần mềm mô-đun, có thể tái sử dụng và dễ bảo trì.
Đó là một nguyên tắc thiết kế, trong đó Luồng điều khiển được "nhận" từ thư viện được viết chung hoặc mã có thể sử dụng lại.
Để hiểu rõ hơn, hãy xem cách chúng tôi đã sử dụng mã trong những ngày đầu viết mã. Trong các ngôn ngữ thủ tục / truyền thống, logic nghiệp vụ thường kiểm soát luồng của ứng dụng và "Gọi" mã / chức năng chung hoặc có thể tái sử dụng. Ví dụ: trong một ứng dụng Console đơn giản, luồng điều khiển của tôi được kiểm soát bởi các hướng dẫn của chương trình của tôi, có thể bao gồm các lệnh gọi đến một số chức năng có thể tái sử dụng chung.
print ("Please enter your name:");
scan (&name);
print ("Please enter your DOB:");
scan (&dob);
//More print and scan statements
<Do Something Interesting>
//Call a Library function to find the age (common code)
print Age
Ngược lại, với IoC, các Khung công tác là mã có thể tái sử dụng "Gọi" logic nghiệp vụ.
Ví dụ, trong một hệ thống dựa trên cửa sổ, một khuôn khổ sẽ có sẵn để tạo các phần tử giao diện người dùng như các nút, menu, cửa sổ và hộp thoại. Khi tôi viết logic nghiệp vụ của ứng dụng của mình, các sự kiện của khung sẽ gọi mã logic nghiệp vụ của tôi (khi một sự kiện được kích hoạt) chứ KHÔNG phải ngược lại.
Mặc dù, mã của khung công tác không nhận biết được logic nghiệp vụ của tôi, nhưng nó vẫn sẽ biết cách gọi mã của tôi. Điều này đạt được bằng cách sử dụng sự kiện / đại biểu, lệnh gọi lại, v.v. Ở đây Điều khiển luồng là "Đảo ngược".
Vì vậy, thay vì phụ thuộc vào luồng điều khiển trên các đối tượng bị ràng buộc tĩnh, luồng phụ thuộc vào đồ thị đối tượng tổng thể và mối quan hệ giữa các đối tượng khác nhau.
Dependency Injection là một mẫu thiết kế thực hiện nguyên tắc IoC để giải quyết các phụ thuộc của các đối tượng.
Nói cách đơn giản hơn, khi bạn đang cố gắng viết mã, bạn sẽ tạo và sử dụng các lớp khác nhau. Một lớp (Lớp A) có thể sử dụng các lớp khác (Lớp B và / hoặc D). Vì vậy, Lớp B và D là các phụ thuộc của lớp A.
Một phép tương tự đơn giản sẽ là một chiếc Xe hạng. Một chiếc ô tô có thể phụ thuộc vào các lớp khác như Động cơ, Lốp xe và hơn thế nữa.
Dependency Injection gợi ý rằng thay vì các lớp Phụ thuộc (Class Car ở đây) tạo ra các phần phụ thuộc của nó (Class Engine và class Tire), lớp nên được đưa vào với phiên bản cụ thể của phần phụ thuộc.
Hãy hiểu bằng một ví dụ thực tế hơn. Cân nhắc rằng bạn đang viết TextEditor của riêng mình. Trong số những thứ khác, bạn có thể có một trình kiểm tra chính tả cung cấp cho người dùng một phương tiện để kiểm tra lỗi chính tả trong văn bản của họ. Một cách triển khai đơn giản của mã như vậy có thể là:
Class TextEditor
{
//Lot of rocket science to create the Editor goes here
EnglishSpellChecker objSpellCheck;
String text;
public void TextEditor()
{
objSpellCheck = new EnglishSpellChecker();
}
public ArrayList <typos> CheckSpellings()
{
//return Typos;
}
}
Ngay từ cái nhìn đầu tiên, tất cả đều trông hồng hào. Người dùng sẽ viết một số văn bản. Nhà phát triển sẽ nắm bắt văn bản và gọi chức năng CheckSpellings và sẽ tìm thấy danh sách các Typos mà anh ta sẽ hiển thị cho Người dùng.
Mọi thứ dường như hoạt động tốt cho đến một ngày đẹp trời khi một người dùng bắt đầu viết tiếng Pháp trong Trình chỉnh sửa.
Để cung cấp hỗ trợ cho nhiều ngôn ngữ hơn, chúng tôi cần có nhiều Trình kiểm tra chính tả hơn. Có thể là tiếng Pháp, tiếng Đức, tiếng Tây Ban Nha, v.v.
Ở đây, chúng tôi đã tạo một mã kết hợp chặt chẽ với SpellChecker "tiếng Anh" được kết hợp chặt chẽ với lớp TextEditor của chúng tôi, có nghĩa là lớp TextEditor của chúng tôi phụ thuộc vào EnglishSpellChecker hay nói cách khác EnglishSpellCheker là phần phụ thuộc của TextEditor. Chúng ta cần loại bỏ sự phụ thuộc này. Hơn nữa, Trình soạn thảo văn bản của chúng tôi cần một cách để giữ tham chiếu cụ thể của bất kỳ Trình kiểm tra chính tả nào dựa trên quyết định của nhà phát triển tại thời điểm chạy.
Vì vậy, như chúng ta đã thấy trong phần giới thiệu của DI, nó gợi ý rằng lớp nên được đưa vào các phụ thuộc của nó. Vì vậy, trách nhiệm của mã gọi là đưa tất cả các phụ thuộc vào lớp / mã được gọi. Vì vậy, chúng tôi có thể cấu trúc lại mã của mình như
interface ISpellChecker
{
Arraylist<typos> CheckSpelling(string Text);
}
Class EnglishSpellChecker : ISpellChecker
{
public override Arraylist<typos> CheckSpelling(string Text)
{
//All Magic goes here.
}
}
Class FrenchSpellChecker : ISpellChecker
{
public override Arraylist<typos> CheckSpelling(string Text)
{
//All Magic goes here.
}
}
Trong ví dụ của chúng tôi, lớp TextEditor sẽ nhận được phiên bản cụ thể của loại ISpellChecker.
Bây giờ, phụ thuộc có thể được đưa vào Constructor, một Thuộc tính Công cộng hoặc một phương thức.
Hãy thử thay đổi lớp của chúng ta bằng Constructor DI. Lớp TextEditor đã thay đổi sẽ trông giống như sau:
Class TextEditor
{
ISpellChecker objSpellChecker;
string Text;
public void TextEditor(ISpellChecker objSC)
{
objSpellChecker = objSC;
}
public ArrayList <typos> CheckSpellings()
{
return objSpellChecker.CheckSpelling();
}
}
Để mã gọi, trong khi tạo trình soạn thảo văn bản có thể đưa Loại SpellChecker thích hợp vào bản sao của TextEditor.
Bạn có thể đọc toàn bộ bài viết tại đây