Mình nghĩ @Hung_Nguyen12 trả lời chuẩn rồi. Còn vụ tranh luận đa thừa kế hay thuần ảo … theo mình không quan trọng lắm ^^ (hầu hết các tài liệu đều gọi interface là thuần abstract và dùng cho vụ đa thừa kế).
Còn vụ what, why, when thì mình giải thích qua 1 chút ở 1 số case
Case1: Giả sử bạn viết CRUD cho product
public void addProduct(Product p){
//code here
}
Giờ bạn muốn làm unit-test thì sao? Bạn sẽ tạo ra 1 p = new Product(); //fake. Thế nhưng nếu 1 người nào đó dùng hàm của bạn, nhưng không muốn bị bó buộc vào object Product đó, vì họ chẳng biết Product cần phải được tạo ra như thế nào (giả sử lib của bạn đã được compile). So sánh với:
public void addProduct(IProduct p){
//code here
}
Giờ mình gom những cái gì thực sự cần thiết ra 1 interface, họ có thể tự truyền 1 new MockProduct implement IProduct là được, dễ dàng hơn rất nhiều phải không? Nguyên lý này được gọi là Liskov Duck (chữ L trong SOLID), nghĩa là bạn thiết kế ra 1 con vịt tiêu chuẩn mà có thể gắn pin AA, con thỏ, con ó … vào được, miễn là pin đó phù hợp với thiết kế IPin. Việc gom tạo interface cũng giúp người sử dụng lib dễ dàng hiểu các API nào mình expose cho họ, cũng giống như bạn cầm cái điều khiển quạt, bạn bấm 1,2,3 … chứ đâu cần biết trong cái điều khiển đó, nguyên lý hoạt động ra sau (ví dụ, bấm 1, tạo xung điện, kết nối bộ phần phát hồng ngoại, gửi tín hiệu hồng ngoại, quạt nhận tín hiệu hồng ngoại, chuyển đổi xung, biến trở ngắt để cuộn cảm chuyển mạch sang cảm ứng điện mới …Who cares?)
Case2: trong lập trình sự kiện (event-driven), giả sử sự kiện click
btnCheckListener = new OnClickListener {
click(event){
//implement ở đây
}
}
btnCheck.setOnClickListener(btnCheckListener);
interface OnClickListener {
click(event);
}
Việc truyền 1 class implement OnClickListener vì trong cái event-loop nghe sự kiện, mình sẽ phải gọi cái action xử lý khi có sự kiện đó. Lúc đó, mình gọi action nào nếu mình không qui định trong inteface. Việc qui định click function sẽ giúp mình thống nhất cách gọi (chứ ông thì đặt click, ông đặt push, … mình phải wrap hết các trường hợp đó có mà a ma toi)
//event-loop
while(true){
listener.click(e);
}
Case3: vụ đa thừa kế (thực tế thì thừa kế mỗi tên hàm, chứ nội dung thì có thừa kế đâu). Ví dụ mình muốn viết 1 collection.
public class MyCollection implements Serializable, Iterable, Traversable {
public void iterate(){};
public void next(){};
public void prev(){};
public void last(){};
public void first(){};
public void move(index i){};
}
Rõ ràng mình cần MyCollection của mình phải iterate được, phải move qua move lại giữa các element, … thế nên mình implement các Interface chuẩn thì: thứ nhất không bỏ sót tính năng, thứ hai collection này sau có thể dùng như các collection chuẩn khác (người dùng lib đỡ phải học)
Interface được dùng trong rất nhiều design pattern kiểu Protocol Oriented (tức là mình chỉ cần hiểu giao thức, còn phía trong đâu cần biết) như Command, Repository …