Giới thiệu
Chúng ta cùng đến với nguyên lý thứ 3 trong nguyên lý SOLID. Chúng ta sẽ cùng nhau bàn luận về nguyên lý này nhưng trước tiên hãy xem phát biểu của nguyên lý này như thế nào đã nhé.
Phát biểu: Các instance của lớp con có thể thay thế các instance của lớp cha mà không làm thay đổi tính đúng đắn của chương trình
Ví dụ Liskov substitution principle
Mình xét ví dụ trong thực tế cuộc sống để dễ hiểu hơn. Lúc còn nhỏ chúng ta đều nghĩ rằng con chim nào cũng biết bay, cứ ai nhắc đến bất cứ một con chim nào thì điều chúng ta nghĩ đến là nó sẽ biết bay, rồi hình dáng nó như thể nào,.. Bỗng một ngày bạn xem chương trình thế giới động vật, và bạn thấy chương trình giới thiệu với chúng ta loài chim cánh cụt. Chim cánh cụt cũng là một loài chim, nhưng nó lại không biết bay. Quay trở lại nếu chúng ta thiết kế một class Bird và nó luôn luôn bay được cho đến khi có chim cánh cụt xuất hiện thì rất khó để xử lý. Đây là một ví dụ để chỉ ra những gì mình nói ở trên là vi phạm nguyên tắc Liskov substitution principle.
Ta xem đoạn code dưới đây
public abstract class Bird { public abstract void fly(); public abstract void sound(); }
public class Sparrows extends Bird { @Override public void fly() { System.out.println("Sparrows fly"); } @Override void sound() { System.out.println("Sparrows sound"); } }
public class Penguins extends Bird { @Override public void fly() throws UnsupportedOperationException{ throw new UnsupportedOperationException("UnsupportedOperationException"); } @Override void sound() { System.out.println("Penguins sound"); } }
public class Main { public static void main(String[] args) { List<Bird> birds = new ArrayList<>(); for(Bird bird : birds) { bird.fly(); } } }
Ở đây, ta có class Sparrows thừa kế Bird và dĩ nhiên là nó bay được. Mọi chuyện đến đây sẽ tốt đẹp và code ở trong vòng for sẽ chạy ngon lành. Cho đến khi bạn có class Penguins, và con chim Penguins này không biết bay. Dễ thôi mình vẫn cho nó extends từ bird nhưng method fly() mình sẽ quăng exception là xong.
Thế nhưng mà, quay laị vòng lặp for ở hàm main hồi nảy, nếu như trong danh sách các con chim đó mà có một con chim nào là Penguins thì sao? Chương trình mình sẽ quăng Exception vì chương trình của chúng ta đã vi phạm Liskov substitution principle.
Mặc dù hình vuông là hình chữ nhật, chim cánh cụt là chim không có nghĩa chúng nên kế thừa nhau. Chúng chỉ kế thừa khi instance của class con có thể thay thế class cha.
Một ví dụ điển hình áp dụng nguyên lý Liskov substitution principle tăng tính linh hoạt của chương trình là Stream API có thể hoạt động cho ArrayList, LinkedList, Set etc, tất cả chúng đều implement từ Collection interface, các instance của chúng đều có thể thay thế Collection mà không làm thay đổi tín đúng đắn của chương trình.