Nguyên lý SOLID trong lập trình hướng đối tượng – và ví dụ sử dụng C# – p4

Tìm hểu về “I” – ISP (Interface Segregation principle)

Thay vì dùng 1 interface lớn, ta nên tách thành nhiều interface nhỏ, với nhiều mục đích cụ thể

Nguyên lý này khá dễ hiểu. Hãy tưởng tượng chúng ta có 1 interface lớn, khoảng 100 methods. Việc implements sẽ khá cực khổ, ngoài ra còn có thể dư thừa vì 1 class không cần dùng hết 100 method. Khi tách interface ra thành nhiều interface nhỏ, gồm các method liên quan tới nhau, việc implement và quản lý sẽ dễ hơn.

Dưới đây minh họa cho một thiết kế sai khi không sử dụng nguyên lý phân tách interface. Các lớp kế thừa interface phải thực thi các phương thức không cần thiết của interface đó.

public interface Animal {
    void fly();
 
    void run();
 
    void bark();
}
 
public class Bird implements Animal {
    public void bark() { /* do nothing */
    }
 
    public void run() {
        // write code about running of the bird
    }
 
    public void fly() {
        // write code about flying of the bird
    }
}
 
public class Cat implements Animal {
    public void fly() {
        throw new Exception("Undefined cat property");
    }
 
    public void bark() {
        throw new Exception("Undefined cat property");
    }
 
    public void run() {
        // write code about running of the cat
    }
}
 
public class Dog implements Animal {
    public void fly() {
    }
 
    public void bark() {
        // write code about barking of the dog
    }
 
    public void run() {
        // write code about running of the dog
    }
}

Bạn có thể dễ dàng thấy ở trên class Dog cũng sẽ implement lại phương thức fly(). Gượm đã Dog cũng có thể fly sao 🙂

Hãy tưởng tượng khi interface Animal ngày càng mở rộng, thì các lớp kế thừa phải tiếp tục implement các phương thức không cần thiết. Đã đến lúc nghĩ đến việc phân tách interface thành các interface nhỏ hơn. Mỗi interface chỉ nên làm những nhiệm vụ có mối liên hệ chặt chẽ với nhau.

Dưới đây là thiết kế sử dụng nguyên lý phân tách interface:

public interface Flyable {
    void fly();
}
 
public interface Runnable {
    void run();
}
 
public interface Barkable {
    void bark();
}
 
public class Bird implements Flyable, Runnable {
    public void run() {
        // write code about running of the bird
    }
 
    public void fly() {
        // write code about flying of the bird
    }
}
 
public class Cat implements Runnable {
    public void run() {
        // write code about running of the cat
    }
}
 
public class Dog implements Runnable, Barkable {
    public void bark() {
        // write code about barking of the dog
    }
 
    public void run() {
        // write code about running of the dog
    }
}

Như vậy, việc mở rộng các interface sẽ không còn là vấn đề.

Bàn thêm vài nguyên lý:

Khi xây dựng một lớp đối tượng, đặc biệt là những lớp trừu tượng (abstract class), nhiều người thường có xu hướng để cho lớp đối tượng thực hiện càng nghiều chức năng càng tốt, đưa thật nhiều thuộc tính và phương thức vào lớp đối tượng đó. Những lớp đối tượng như vậy được gọi là những lớp đối tượng có interface bị “ô nhiễm” (polluted interface).

Khi một lớp đối tượng có interface bị “ô nhiễm”, nó sẽ trở nên cồng kềnh. Một thực thể phần mềm nào đó chỉ cần thực hiện một công việc đơn giản mà lớp đối tượng này hỗ trợ buộc phải làm việc với toàn bộ interface của lớp đối tượng đó. Đặc biệt đối với lớp trừu tượng có interface bị “ô nhiễm”, một số lớp kế thừa chỉ quan tâm đến một phần interface của nó nhưng bị buộc phải thực hiện việc cài đặt cho cả phần interface không hề có ý nghĩa đối với chúng. Điều dẫn đến sự dư thừa không cần thiết trong các thực thể phần mềm. Quan trọng hơn nữa, việc buộc các lớp kế thừa phụ thuộc vào phần interface mà chúng không sử dụng đến sẽ làm tăng sự kết dính (coupling) giữa các thực thể phần mềm. Một khi sự nâng cấp, mở rộng diễn ra, đòi hỏi phần interface đó thay đổi, các lớp kế thừa này bị buộc phải chỉnh sửa. Điều này làm cho chúng vi phạm nguyên lý Open-Close.

Nguyên lý Phân tách interface có mối liên hệ (nhưng không mật thiết lắm) với nguyên lý Open-Close. Sự vi phạm nguyên lý Phân tách interface có khả năng dẫn đến sự vi phạm nguyên lý Open-Close (xem phân tích ở trên).

Interface bị “ô nhiễm” của lớp đối tượng nên được phân tách ngay khi có thể để tránh khả năng dẫn đến sự vi phạm nguyên lý Open-Close. Việc phân tách interface có thể được thể hiện thông qua việc truyền từng tham số riêng, cụ thể vào một hàm hơn là truyền một tham số chung, tổng quát trong khi hàm đó chỉ sử dụng một phần công việc được hỗ trợ bởi tham số chung, tổng quát này. Nó cũng có thể được thể hiện thông qua việc tăng thêm mức độ trừu tượng trong cây kế thừa.

Interface chung của một bộ phận lớp kế thừa được tổng hợp lại trong một lớp cơ sở. Và lớp cơ sở này lại kế thừa từ lớp cơ sở ban đầu. Như vậy những lớp kế thừa khác không bị phụ thuộc vào phần interface mà chúng không sử dụng đến của những lớp kế thừa kia.

Trong trường hợp sau khi phân tách interface, một số lớp kế thừa muốn sử dụng những phần interface đã phân tách, chúng có thể thực hiện việc đa kế thừa từ những lớp hỗ trợ những phần interface này hoặc thực hiện “composition” những đối tượng thuộc những các lớp đó.

Một số bài viết tham khảo

http://laptrinh.vn/d/3839-nguyen-ly-solid-trong-thiet-ke-huong-doi-tuong-p4-interface-sergregation-principle.html
http://codebuild.blogspot.com/2010/09/oop-solid-rules-interface-segregation.html
SOLID design principles in .NET: the Interface Segregation Principle

Written by thaotrinh