Interface Segregation Principle (ISP)¶
Khái niệm¶
Interface Segregation Principle là nguyên lý thứ tư trong bộ nguyên lý SOLID, được phát biểu bởi Robert C. Martin. Nguyên lý này quy định:
- Đừng ép client phụ thuộc vào những interface nó không dùng
- Thay vì một interface to, hãy chia thành nhiều interface nhỏ chuyên biệt
- Mỗi client chỉ nên biết về những method mà nó thực sự cần
Định nghĩa chính thức¶
"No client should be forced to depend on methods it does not use."
Tại sao ISP quan trọng?¶
- Giảm coupling: Client chỉ phụ thuộc vào những gì cần thiết
- Tăng flexibility: Dễ dàng thay đổi implementation mà không ảnh hưởng client
- Cải thiện maintainability: Thay đổi interface không làm hỏng code không liên quan
- Tránh "fat interfaces": Interface không bị phình to và khó quản lý
Ví dụ vi phạm ISP¶
Đây là một ví dụ SAI - vi phạm nguyên lý ISP:
interface Animal {
void eat();
void fly();
void swim();
void run();
void crawl();
void climb();
}
// Chim chỉ cần eat() và fly(), nhưng bắt buộc implement tất cả methods
class Bird implements Animal {
@Override
public void eat() {
System.out.println("Chim ăn hạt");
}
@Override
public void fly() {
System.out.println("Chim bay");
}
@Override
public void swim() {
// Không cần thiết nhưng phải implement
throw new UnsupportedOperationException("Chim không bơi được");
}
@Override
public void run() {
// Không cần thiết nhưng phải implement
throw new UnsupportedOperationException("Chim không chạy được");
}
@Override
public void crawl() {
throw new UnsupportedOperationException("Chim không bò được");
}
@Override
public void climb() {
throw new UnsupportedOperationException("Chim không leo được");
}
}
// Cá cũng gặp vấn đề tương tự
class Fish implements Animal {
@Override
public void eat() {
System.out.println("Cá ăn tảo");
}
@Override
public void swim() {
System.out.println("Cá bơi");
}
@Override
public void fly() {
throw new UnsupportedOperationException("Cá không bay được");
}
@Override
public void run() {
throw new UnsupportedOperationException("Cá không chạy được");
}
@Override
public void crawl() {
throw new UnsupportedOperationException("Cá không bò được");
}
@Override
public void climb() {
throw new UnsupportedOperationException("Cá không leo được");
}
}
Vấn đề nghiêm trọng:¶
- Forced implementation: Class buộc phải implement methods không cần thiết
- Runtime errors: Nhiều methods throw exceptions khi được gọi
- Fat interface: Interface quá lớn và khó maintain
- Tight coupling: Client phụ thuộc vào toàn bộ interface lớn
- Khó testing: Phải mock tất cả methods, kể cả không dùng
- Violation of Single Responsibility: Interface làm quá nhiều việc
Giải pháp tuân thủ ISP¶
Chia interface lớn thành nhiều interface nhỏ chuyên biệt:
Bước 1: Tách thành các interface chuyên biệt¶
// Interface cơ bản - tất cả animals đều cần
interface Eater {
void eat();
}
// Các interface chuyên biệt cho từng khả năng
interface Flyer {
void fly();
void land();
}
interface Swimmer {
void swim();
void dive();
}
interface Runner {
void run();
void walk();
}
interface Crawler {
void crawl();
}
interface Climber {
void climb();
}
Bước 2: Implement chỉ những gì cần thiết¶
// Chim chỉ implement những interface cần thiết
class Bird implements Eater, Flyer {
@Override
public void eat() {
System.out.println("Chim ăn hạt và sâu");
}
@Override
public void fly() {
System.out.println("Chim bay lên trời");
}
@Override
public void land() {
System.out.println("Chim đậu xuống cành");
}
}
// Cá chỉ implement những interface cần thiết
class Fish implements Eater, Swimmer {
@Override
public void eat() {
System.out.println("Cá ăn tảo và plankton");
}
@Override
public void swim() {
System.out.println("Cá bơi trong nước");
}
@Override
public void dive() {
System.out.println("Cá lặn xuống đáy");
}
}
// Chó có thể chạy và bơi
class Dog implements Eater, Runner, Swimmer {
@Override
public void eat() {
System.out.println("Chó ăn thức ăn");
}
@Override
public void run() {
System.out.println("Chó chạy nhanh");
}
@Override
public void walk() {
System.out.println("Chó đi bộ");
}
@Override
public void swim() {
System.out.println("Chó bơi");
}
@Override
public void dive() {
System.out.println("Chó lặn nhẹ");
}
}
// Rắn có thể bò và leo
class Snake implements Eater, Crawler, Climber {
@Override
public void eat() {
System.out.println("Rắn ăn chuột");
}
@Override
public void crawl() {
System.out.println("Rắn bò");
}
@Override
public void climb() {
System.out.println("Rắn leo cây");
}
}
Bước 3: Sử dụng type-safe¶
public class AnimalManager {
// Method cho animals bay được
public static void makeAnimalFly(Flyer flyer) {
flyer.fly();
flyer.land();
}
// Method cho animals bơi được
public static void makeAnimalSwim(Swimmer swimmer) {
swimmer.swim();
swimmer.dive();
}
// Method cho animals chạy được
public static void makeAnimalRun(Runner runner) {
runner.run();
runner.walk();
}
// Method cho tất cả animals (chỉ cần ăn)
public static void feedAnimal(Eater eater) {
eater.eat();
}
public static void main(String[] args) {
Bird bird = new Bird();
Fish fish = new Fish();
Dog dog = new Dog();
Snake snake = new Snake();
// Tất cả đều có thể ăn
feedAnimal(bird);
feedAnimal(fish);
feedAnimal(dog);
feedAnimal(snake);
// Chỉ bird có thể bay
makeAnimalFly(bird);
// Fish và Dog có thể bơi
makeAnimalSwim(fish);
makeAnimalSwim(dog);
// Chỉ Dog có thể chạy
makeAnimalRun(dog);
// Compiler sẽ báo lỗi nếu pass sai type
// makeAnimalFly(fish); // Compile error!
// makeAnimalRun(bird); // Compile error!
}
}
Ví dụ thực tế trong phát triển phần mềm¶
Document Management System¶
Sai - Fat Interface:
interface DocumentProcessor {
void createDocument();
void editDocument();
void printDocument();
void faxDocument();
void scanDocument();
void emailDocument();
void archiveDocument();
}
// ReadOnlyUser không cần create, edit, fax, scan
class ReadOnlyUser implements DocumentProcessor {
@Override
public void createDocument() {
throw new UnsupportedOperationException("Read-only user cannot create");
}
@Override
public void editDocument() {
throw new UnsupportedOperationException("Read-only user cannot edit");
}
// ... nhiều methods không cần thiết
}
Đúng - Segregated Interfaces:
// Chia thành các interface chuyên biệt
interface DocumentCreator {
void createDocument();
}
interface DocumentEditor {
void editDocument();
}
interface DocumentPrinter {
void printDocument();
}
interface DocumentFaxer {
void faxDocument();
}
interface DocumentScanner {
void scanDocument();
}
interface DocumentEmailer {
void emailDocument();
}
interface DocumentArchiver {
void archiveDocument();
}
// Admin có full permissions
class AdminUser implements DocumentCreator, DocumentEditor,
DocumentPrinter, DocumentFaxer,
DocumentScanner, DocumentEmailer,
DocumentArchiver {
// Implement tất cả methods
}
// Regular user chỉ có một số permissions
class RegularUser implements DocumentCreator, DocumentEditor,
DocumentPrinter, DocumentEmailer {
// Chỉ implement những gì cần thiết
}
// Guest user chỉ có thể đọc và in
class GuestUser implements DocumentPrinter {
@Override
public void printDocument() {
System.out.println("Guest printing document");
}
}
ISP vs Other SOLID Principles¶
Kết hợp với SRP¶
- SRP: Một class chỉ có một lý do thay đổi
- ISP: Một interface chỉ chứa methods liên quan với nhau
Kết hợp với OCP¶
- ISP giúp OCP bằng cách tạo ra interfaces ổn định
- Thêm functionality mới không ảnh hưởng existing interfaces
Kết hợp với LSP¶
- ISP đảm bảo substitution an toàn
- Subclass không bị ép implement methods không phù hợp
Kết hợp với DIP¶
- ISP tạo ra abstractions tốt hơn cho DIP
- High-level modules phụ thuộc vào focused interfaces
Guidelines để áp dụng ISP¶
Khi nào cần áp dụng ISP:¶
- Interface có quá nhiều methods (> 7-10 methods)
- Clients chỉ sử dụng một phần nhỏ interface
- Nhiều empty implementations hoặc exceptions trong methods
- Interface có nhiều responsibilities khác nhau
- Thay đổi interface ảnh hưởng nhiều clients không liên quan
Cách chia interface hiệu quả:¶
- Group by functionality: Nhóm methods theo chức năng
- Follow client needs: Chia theo nhu cầu của clients
- Single responsibility: Mỗi interface một trách nhiệm
- Cohesion: Methods trong interface liên quan chặt chẽ
- Stable abstractions: Interface ít thay đổi
Tránh over-segregation:¶
- Quá nhiều interfaces nhỏ cũng gây phức tạp
- Balance giữa segregation và simplicity
- Consider context: Domain và requirements cụ thể
Lợi ích của việc tuân thủ ISP¶
Giảm coupling¶
- Client chỉ phụ thuộc những gì cần thiết
- Thay đổi interface không ảnh hưởng clients không liên quan
Tăng cohesion¶
- Interface tập trung vào một nhóm chức năng liên quan
- Code dễ hiểu và maintain
Flexibility cao¶
- Dễ dàng thêm/xóa capabilities
- Composition linh hoạt hơn inheritance
Testing dễ dàng¶
- Mock chỉ những interfaces cần thiết
- Test cases focused và rõ ràng
Reusability tốt¶
- Small interfaces dễ reuse
- Không bị ràng buộc bởi unnecessary dependencies
Kết luận¶
Interface Segregation Principle giúp tạo ra:
- Focused interfaces với single responsibility
- Loose coupling giữa components
- High cohesion trong từng interface
- Flexible design dễ maintain và extend
Nhớ rằng: "Many client-specific interfaces are better than one general-purpose interface" - đây là chìa khóa để thiết kế interfaces hiệu quả và bền vững!