VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
OOP và thiết kếSOLID
Bài 12/12
5 phút

DIP và dependency injection

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt

OrderService tự tạo SqlOrderRepository bên trong. Muốn chạy test thì phải có database thật, muốn đổi sang lưu file thì phải sửa OrderService. DIP là nguyên tắc cuối của SOLID, còn dependency injection là cách làm phổ biến nhất để đạt được nó.

Khái niệm

🔌 DIP (Dependency Inversion Principle): code nghiệp vụ không phụ thuộc trực tiếp vào class cụ thể lo database hay email, mà cả hai cùng phụ thuộc vào interface.

💉 Dependency injection (DI): class nhận những thứ nó cần qua constructor, thay vì tự new bên trong.

Ví dụ

var repo = new SqlOrderRepository();
var service = new OrderService(repo);
service.Place("Bút bi");

interface IOrderRepository
{
    void Save(string item);
}

class SqlOrderRepository : IOrderRepository
{
    public void Save(string item) =>
        Console.WriteLine($"SQL: lưu {item}");
}

class OrderService
{
    private readonly IOrderRepository _repo;

    public OrderService(IOrderRepository repo)
    {
        _repo = repo;
    }

    public void Place(string item) => _repo.Save(item);
}
  • OrderService chỉ biết IOrderRepository, không biết có SQL. Đó là DIP.
  • OrderService nhận repository qua constructor. Đó là DI. PlaceOrder ở bài Interface đã làm đúng như vậy.
  • Nơi tạo OrderService quyết định dùng repository nào.
  • Trong ASP.NET Core, framework tự tạo và truyền các object này. Bạn chỉ khai báo interface nào ứng với class nào.
Trước Sau implement OrderService SqlOrderRepository OrderService IOrderRepository SqlOrderRepository
Trước: phụ thuộc thẳng vào SQL. Sau: cả hai hướng về interface

Thử ngay

Chép ví dụ trên vào Program.cs. Thêm class dưới đây vào cuối file, rồi đổi new SqlOrderRepository() ở dòng đầu thành new FakeOrderRepository():

class FakeOrderRepository : IOrderRepository
{
    public void Save(string item) =>
        Console.WriteLine($"Giả lập: lưu {item}");
}

Đoán trước khi chạy: chương trình in ra dòng nào?

Xem kết quả
Giả lập: lưu Bút bi

OrderService gọi Save của object được truyền vào, nên in dòng của FakeOrderRepository. Viết test không cần database thật cũng theo cách này: truyền vào một repository giả.

Lỗi hay gặp

Tự new bên trong class. OrderService bị khoá cứng vào SQL.

// SAI — không đổi được, không test được
class OrderService
{
    private readonly SqlOrderRepository _repo =
        new SqlOrderRepository();

    public void Place(string item) => _repo.Save(item);
}
// ĐÚNG — nhận interface qua constructor
class OrderService
{
    private readonly IOrderRepository _repo;

    public OrderService(IOrderRepository repo)
    {
        _repo = repo;
    }

    public void Place(string item) => _repo.Save(item);
}

Nhận class cụ thể qua constructor. Có DI nhưng vẫn phụ thuộc vào SQL, nên chưa đạt DIP.

// SAI — vẫn chỉ nhận được SqlOrderRepository
class OrderService
{
    private readonly SqlOrderRepository _repo;

    public OrderService(SqlOrderRepository repo)
    {
        _repo = repo;
    }
}
// ĐÚNG — kiểu tham số là interface
class OrderService
{
    private readonly IOrderRepository _repo;

    public OrderService(IOrderRepository repo)
    {
        _repo = repo;
    }
}

Tóm tắt

  • DIP: code nghiệp vụ phụ thuộc vào interface, không phụ thuộc vào class cụ thể.
  • DI: nhận phụ thuộc qua constructor, không tự new bên trong.
  • Nhờ DI, đổi cách làm hay viết test chỉ cần truyền object khác vào.
  • Constructor nên nhận interface, không nhận class cụ thể.

Tự kiểm tra

0/3 câu
Câu 1

class ReportService { private readonly PdfExporter _pdf = new PdfExporter(); } Vấn đề chính là gì?

Câu 2

Constructor nào đúng tinh thần DIP?

Câu 3

Lợi ích lớn nhất của DI khi viết test là gì?

ISP — Interface nhỏ, đúng việc

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt