Dependency Inversion Principle là gì?

Nguyên tắc Dependency Inversion Principle (DIP) được phát biểu như sau.

"High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions."

Những module cấp cao không nên phụ thuộc vào những module thấp hơn. Thay vào đó nó nên phụ thuộc vào sự trừu tượng hóa.

Sự trừu tượng hóa, không nên phụ thuộc vào chi tiết, chi tiết nên phụ thuộc vào trừu tượng hóa.

DIP là nguyên tắc khó hiểu nhất trong SOLID, để hiểu nó đầu tiên chúng ta phải hiểu, sự phụ thuộc (dependency) là gì.

Sự phụ thuộc, dependency là gì?

Trong phần mềm sự phụ thuộc (dependency) nghĩa là một phần mềm này phụ thuộc vào phần mềm hoặc thư viện khác.

Khi cài game, nó đòi hỏi DirectX 11.0, tức là game này phụ thuộc vào DirectX 11.0.

Khi muốn mã hóa, bạn sử dụng thư viện cipher, như vậy phần mềm naỳ phụ thuộc vào thư viện cipher.

Khi class A sử dụng class B thì class A phụ thuộc vào class B.

Sự phụ thuộc cứng (Concrete Dependency):

Khi bạn sử dụng trực tiếp lớp này trong lớp khác, chúng ta gọi đây là sự phụ thuộc cứng.

public class ConcreteDependency
{
   public void Print()
   {
       // Lớp ConcreteDependency sử dụng trực tiếp lớp HP Printer
       HPPrinter worker = new HPPrinter();
       worker.Print();
   }
}

Sử dụng:

ConcreteDependency concreteDependency = new ConcreteDependency();
concreteDependency.Print();

class ConcreteDependency sử dụng trực tiếp class HPPrinter trong hàm Print, do vậy lớp ConcreteDependency phụ thuộc cứng vào class HPPrinter. Trong trường hợp này, ConcreteDependency được xem là Module cấp cao (High Level Module), trong khi HPPrinter là module cấp thấp (Low Level Module). Vì class ConcreteDependency sử dụng HPPrinter bên trong nó. 

High Level Module phụ thuộc trực tiếp low level module

Dấu mũi tên không cắt biểu hiện cho việc class ConcreteDependency sử dụng trực tiếp class HPPrinter.

Sự phụ thuộc mềm hay sự phụ thuộc trừu tượng (Abstraction Dependency).

Khi bạn không sử dụng trực tiếp lớp này trong lớp khác, mà thông qua trừu tượng (interface) thì đây gọi là sự phụ thuộc trừu tượng.

public class AbstractDependency
{
   public void Print(IPrinter printer)
   {
       printer.Print();
   }
}

Thực thi:

AbstractDependency abstractDependency = new AbstractDependency();
IPrinter hpPrinter = new HPPrinter();
IPrinter canonPrinter = new CanonPrinter();

abstractDependency.Print(hpPrinter);
abstractDependency.Print(canonPrinter);

Với phụ thuộc trừu tượng, hay sự phụ thuộc lỏng, khi thực thi chương trình mới thực sự biết được lớp nào đang được sử dụng. Khác hẳn với phụ thuộc cứng, bạn biết lớp đang sử dụng ngay khi tạo ra nó khi viết mã.

Sự phụ thuộc mềm

Mũi tên đứt đoạn biểu hiệu cho sự phụ thuộc mềm

Sự thuộc mềm mang lại cho ta điều gì?

Khi giảm đi sự phụ thuộc cứng, bạn có thể viết các chương trình của mình dưới dạng plugin, dễ dàng mở rộng, thay thế.

Ở trên là ví dụ về cách hệ điều hành sử dụng các máy in. Trước năm 1950, sự phụ thuộc cứng vẫn còn được sử dụng rộng rãi, khi thay máy in mới, các lập trình viên phải viết lại code để thực hiện thao tác chạy máy in. Điều này vô cùng tốn thời gian và dễ phát sinh ra bug. Như vậy hệ điều hành là module cấp cao lại phụ thuộc trực tiếp vào máy in là module cấp thấp hơn.

Sau đó DIP ra đời, nó cho các lập trình viên biết nên viết chương trình dưới dạng plugin, máy in nên phụ thuộc mềm vào hệ điều hành tức là các máy in của các hãng khác nhau đều chạy trên một interface chung là IPrinter. Khi cắm máy in vào máy tính, máy tính chỉ việc gọi hàm Print và không quan tâm đến máy in đang hoạt động như thế nào.

Đây chính là nguyên lý đảo ngược phụ thuộc (Dependency Inversion), các module cấp cao hơn (hệ điều hành), không phụ thuộc vào các module cấp thấp hơn (máy in), thay vào đó nó phụ thuộc vào sự trừu tượng. Các module cấp thấp sẽ được xem là plugin cho các module cấp cao. Do đó việc thay đổi module cấp thấp diễn ra rất dễ dàng.

Sự trừu tượng không phụ thuộc vào chi tiết, do vậy các Interface không cần quan tâm đến các class được viết như thế nào, các class nên phụ thuộc vào interface.

Ví dụ: 

Phát triển một hệ thống gởi thông báo Email đến user.

public class EmailSender
{
    public void SendEmail()
    {
        //Send email
    }
}

public class Notification
{
    private EmailSender _email;
    public Notification()
    {
        _email = new EmailSender();
    }

    public void Send()
    {
        _email.SendEmail();
    }
}

Có thể thấy Notification là class cấp cao, EmailSender là class cấp thấp hơn.

Bây giờ khách hàng muốn gởi cả SMS và Email đến cho user, bạn phải làm sao?

Tạo một lớp SMSSender và chỉnh sửa class Notificaiton. Như vậy bạn vi phạm một lúc hai nguyên tắc, Dependency Inversion về sự đảo ngược phụ thuộc và Open close principle. Software đóng cho việc thay đổi nhưng mở cho việc mở rộng.

Để thỏa mãn hai nguyên tắc trên, bạn phải Refactoring code theo chiều hướng giảm sự phụ thuộc cứng bằng cách tạo ra một interface ISender dùng chung giữa hai class EmailSender và SMSSender.

public class EmailSender : ISender
{
    public void Send()
    {
        //Send email
    }
}

public class EmailSender : ISender
{
    public void Send()
    {
        //Send email
    }
}

public class SMSSender : ISender
{
    public void Send()
    {
        //Send SMS
    }
}

Sử dụng

public class Notification
{
    private ICollection<ISender> _senders;

    public Notification(ICollection<ISender> senders)
    {
        _senders = senders;
    }
    public void Send()
    {
        foreach (var message in _senders)
        {
            message.Send();
        }
    }
}

Giờ đây class Notificaiton phụ thuộc mềm vào interface ISender, nếu khách hàng yêu cầu thêm một phương thức để chuyển tin nhắn ta có thể thêm vào dễ dàng bằng cách sử dụng interface ISender.

Source code:

https://github.com/mt26691/learning-solid/blob/master/SOLID/SOLID.DependencyInversion/Program.cs

Ích lợi của DIP

Dependency inversion principle giúp bạn giảm sự phụ thuộc giữa các module. Khi các module càng phụ thuộc lỏng lẻo (loosely coupled), ta càng dễ dàng thay thế chúng.

Tại sao các module nên phụ thuộc lỏng lẻo vào nhau?

Đây chính là sự khác biệt cơ bản giữa Hardware và Software.

Bạn có thể thay module camera của iPhone 7 vào iPhone 6s được không? Không, đây chính là Hardware, nó khó thay đổi.

Bạn có thể thay thế, nâng cấp phần mềm trên iPhone 7 và iPhone 6s được không? Có, đây chính là Software. Chữ Soft đại diện cho tính mềm, lỏng, dễ thay đổi. Và để software càng dễ thay đổi, thì các thành phần trong nó phải ít phụ thuộc, hoặc phụ thuộc lỏng lẻo với nhau.

Tôi có thể thay thế hoàn toàn phụ thuộc cứng không?

Không, phụ thuộc cứng là không tránh được khi bạn sử dụng các hàm của hệ thống, như String, IO class, tuy nhiên các hàm này thường ổn định và không đổi theo thời gian nên bạn không cần phải quá bận tâm về điều đó.

Các môn học khác

HTTP giúp kết nối các máy tính trên mạng Internet thông qua TCP/IP

Chương trình thường được xây dựng quanh dữ liệu và logic để xử lý chúng. Nói cách khác: Program = Cấu trúc dữ liệu + giải thuật. Do vậy đây là môn học bắt buộc dành cho các lập trình viên.

Khóa học này cung cấp cho ta kiến thức nền tảng về công nghệ Blockchain, Bitcoin. Vì sao nó được gọi là công nghệ của tương lai, vì sao giá Bitcoin lại cao đến vậy. Làm sao để mua Bitcoin, ETH.