IoC introduction chapter 3

Ở phần trước, chúng ta đã tìm hiểu về cách implement nguyên lý IoC bằng việc sử dụng Factory pattern và đạt được mức độ đầu tiên của việc thiết kế các class có ít sự phụ thuộc hơn.
Phần này chúng ta sẽ tìm hiểu việc implement DIP bằng cách tạo các abstract class.

Đầu tiên, chúng ta cần biết nguyên lý DIP nghĩa là gì?
DIP là một trong 5 nguyên lý thiết kế hướng đối tượng được giới thiệu bởi Robert Martin (Uncle Bob)

Phát biểu của nguyên lý như sau:

  1. Các module high-level không nên phụ thuộc vào các module low-level. Cả 2 nên phụ thuộc vào abstract class.
  2. Abstract class không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào abstract class.

Để hiểu hơn về DIP chúng ta cùng xem lại ví dụ ở chapter trước

public class CustomerBusinessLogic
{
    public CustomerBusinessLogic()
    {
    }

    public string GetCustomerName(int id)
    {
        DataAccess _dataAccess = DataAccessFactory.GetDataAccessObj();

        return _dataAccess.GetCustomerName(id);
    }
}

public class DataAccessFactory
{
    public static DataAccess GetDataAccessObj() 
    {
        return new DataAccess();
    }
}

public class DataAccess
{
    public DataAccess()
    {
    }

    public string GetCustomerName(int id) {
        return "Dummy Customer Name"; // get it from DB in real app
    }
}

Trong ví dụ trên, chúng ta đã implemented factory pattern để thực hiện IoC. Nhưng lớp CustomerBusinessLogic vẫn đang sử dụng lớp DataAccess cụ thể. Vì vậy nó vẫn mang tính phụ thuộc, mặc dù chúng ta đã đảo ngược việc tạo đối tượng phụ thuộc cho lớp factory.

Bằng việc sử dụng DIP, chúng ta sẽ khiến lớp CustomerBusinessLogic và lớp DataAccess có ít sự phụ thuộc hơn như sau.

Đầu tiên chúng ta sẽ xác định lớp high-level và lớp low-level. Lớp high-level là lớp phụ thuộc vào lớp khác. Trong ví dụ này, CustomerBusinessLogic phụ thuộc vào lớp DataAccess, vì vậy CustomerBusinessLogic là lớp high-level và DataAccess là lớp low-level. Lúc này, theo nguyên tắc đầu tiên của DIP, CustomerBusinessLogic không nên phụ thuộc vào một đối tượng cụ thể của lớp DataAccess, cả 2 nên phụ thuộc vào abstract class.

Nguyên tắc thứ 2 của DIP là: “Abstract class không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào abstract class”.

Abstract class là gì?
Trừu tượng hóa và đóng gói là những tính chất quan trọng của lập trình hướng đối tượng. Có rất nhiều định nghĩa và các cách diễn giải khác nhau, từ những người khác nhau, nhưng chúng ta có thể hiểu abstract class bằng việc sử dụng ví dụ trên;

Trong tiếng anh, abstraction nghĩa là đang nói tới một thứ gì đó không-cụ-thể. Trong các quy ước lập trình, cả 2 lớp CustomerBusinessLogic và DataAccess là các lớp cụ thể, nghĩa là chúng ta có thể tạo các đối tượng của chúng. Vì vậy, abstraction trong lập trình nghĩa là tạo một interface hoặc một abstract class không-cụ-thể. Điều này có nghĩa rằng chúng ta không thể tạo một đối tượng của interface hoặc một abstract class. Theo DIP, CustomerBusinessLogic (high-level module) không nên phụ thuộc vào một lớp DataAccess cụ thể (low-level module). Cả 2 lớp này nên phụ thuộc vào abstraction, nghĩa là cả 2 lớp nên phụ thuộc vào một interface hoặc một abstract class.

Bây giờ chúng ta sẽ xem nên đặt gì trong interface (hoặc abstract class)? Như các bạn thấy, lớp CustomerBusinessLogic sử dụng phương thức GetCustomerName() của lớp DataAccess (trong thực tế, sẽ có rất nhiều phương thức của customer được đặt trong DataAccess). Vì vậy chúng ta có thể chuyển phương thức GetCustomerName(int id) vào interface như sau:

public interface ICustomerDataAccess
{
    string GetCustomerName(int id);
}

Bây giờ chúng ta sẽ implement lớp ICustomerDataAccess

public class CustomerDataAccess: ICustomerDataAccess
{
    public CustomerDataAccess()
    {
    }

    public string GetCustomerName(int id) {
        return "Dummy Customer Name";        
    }
}

Tiếp theo chúng ta cần thay đổi lớp factory bằng việc trả về ICustomerDataAccess, thay vì trả về lớp DataAccess:

public class DataAccessFactory
{
    public static ICustomerDataAccess GetCustomerDataAccessObj() 
    {
        return new CustomerDataAccess();
    }
}

Tiếp tục thay đổi lớp  CustomerBusinessLogic, bằng việc sử dụng ICustomerDataAccess, thay vì đối tượng DataAccess:

public class CustomerBusinessLogic
{
    ICustomerDataAccess _custDataAccess;

    public CustomerBusinessLogic()
    {
        _custDataAccess = DataAccessFactory.GetCustomerDataAccessObj();
    }

    public string GetCustomerName(int id)
    {
        return _custDataAccess.GetCustomerName(id);
    }
}

Cuối cùng, chúng ta đã implement DIP trong ví dụ trên, nơi module high-level (CustomerBusinessLogic) và module low-level (CustomerDataAccess) phụ thuộc vào abstraction (ICustomerDataAccess). Ngoài ra, abstraction (ICustomerDataAccess) không phục thuộc vào chi tiết (CustomerDataAccess), nhưng chi tiết phụ thuộc vào abstraction.

Toàn bộ code cuối cùng như sau:

public interface ICustomerDataAccess
{
    string GetCustomerName(int id);
}

public class CustomerDataAccess: ICustomerDataAccess
{
    public CustomerDataAccess() {
    }

    public string GetCustomerName(int id) {
        return "Dummy Customer Name";        
    }
}

public class DataAccessFactory
{
    public static ICustomerDataAccess GetCustomerDataAccessObj() 
    {
        return new CustomerDataAccess();
    }
}

public class CustomerBusinessLogic
{
    ICustomerDataAccess _custDataAccess;

    public CustomerBusinessLogic()
    {
        _custDataAccess = DataAccessFactory.GetCustomerDataAccessObj();
    }

    public string GetCustomerName(int id)
    {
        return _custDataAccess.GetCustomerName(id);
    }
}

 

Tóm tắt

Lợi ích của việc implement DIP trong ví dụ trên là lớp CustomerBusinessLogic và CustomerDataAccess có ít sự phụ thuộc, vì CustomerBusinessLogic không phụ thuộc vào lớp DataAccess cụ thể, thay vào đó, nó liên kết với interface ICustomerDataAccess. Vì vậy bây giờ chúng ta có thể dễ dàng sử dụng một lớp khác implement ICustomerDataAccess với một cách implement hoàn toàn khác lớp chúng ta vừa viết ở trên.

Chúng ta vẫn chưa hoàn thiện việc thiết kế các lớp ít sự phụ thuộc, bời vì lớp CustomerBusinessLogic vẫn bao gồm một lớp factory để lấy quan hệ với ICustomerDataAccess.Vấn đề này sẽ được Dependency Injection pattern xử lý. Trong bài tiếp theo, chúng ta sẽ học cách sử dụng DI và Strategy pattern để phát triển tiếp ví dụ này.

 

Series

Phần 1

Phần 2