[Java] Sử dụng interface
Có rất nhiều vấn đề đật ra như interface dùng để làm gì, tại sao dùng interface, dùng interface như thế nào,… Trong bài này mình hy vọng có thể trả lời phần nào đó một cách dễ hiểu nhất có thể vì thông thường nó tương đối trừu tượng =)) và cũng nói luôn là những điều viết dưới đây nằm trong khuôn khổ hiểu biết hạn chế của mình, nếu bạn có thể bổ xung điều gì đó thì thực sự là tuyệt với đối với mình cũng như bạn đọc theo dõi.
Nội dung
1. Sử dụng Interface tạo hành động chung cho đối tượng
Lấy ví dụ luôn nhé. Tưởng tượng rằng hàng ngày bạn học rất nhiều môn, như Toán, Lý, Hóa, Sinh, Anh, Văn, Sử,… mỗi môn một quyển sách hoặc vở, như vậy thật bất tiện khi bạn “soạn sách” tới lớp. Bây giờ mình bày cho bạn một cách mà không cần biết hôm nay học môn gì mà vẫn có sách của môn đó khi đến lớp, đó là sử dụng sách đa năng mà có thể chứa mọi môn học của bạn. Chính mình cũng đang làm cách đó bằng cách cho mọi môn vào 1 cuốn sổ đa năng, đi học chỉ cầm duy nhất nó đi là xong =)).
Trong lập trình thì cuốn sách (sổ) đa năng đó chính là interface của bạn. Bây giờ hãy bắt đầu dùng thử nhé. Bạn có các môn Toán, Lý, Hóa, Sinh,.. như mình vừa nêu, mỗi môn là một class. Bạn có một mảng các cuốn sách đó và cần mang tới trường và chúng ta thử xem bình thường ta làm thế nào?
package cachhoc.net.useinterface.method; class Toan { public void mangSachToiTruong() { System.out.println("Sach Toan duoc mang toi truong"); } } class Ly { public void mangSachToiTruong() { System.out.println("Sach Ly nhe"); } } class Hoa { public void mangSachToiTruong() { System.out.println("Sach Hoa ok"); } } class Sinh { public void mangSachToiTruong() { System.out.println("Sach Sinh day roi"); } } public class TruongHoc { public static void main(String[] args) { Object[] sach = { new Toan(), new Ly(), new Hoa(), new Sinh() }; for (Object s : sach) { s.mangSachToiTruong(); // loi roi nhe } } }
Bạn thử code đi, bạn sẽ thấy là để tạo ra được mảng sach chúng ta không còn cách nào khác là đặt nó là mảng Object và khi gọi các phương thức mangSachToiTruong() sẽ không thực hiện được.
Bây giờ interface sẽ giúp chúng ta giải quyết vấn đề này. Trước tiên chúng ta tạo một interface Sach có phương thức là mangSachToiTruong();
package cachhoc.net.useinterface.method; public interface Sach { public void mangSachToiTruong(); }
Sau đó chúng ta tạo ra các class Toan, Ly, Hoa, Sinh implements Sach và thực thi phương thức mangSachToiTruong và vấn đề được giải quyết như sau.
package cachhoc.net.useinterface.method; class Toan implements Sach { @Override public void mangSachToiTruong() { System.out.println("Sach Toan duoc mang toi truong"); } } class Ly implements Sach { @Override public void mangSachToiTruong() { System.out.println("Sach Ly nhe"); } } class Hoa implements Sach { @Override public void mangSachToiTruong() { System.out.println("Sach Hoa ok"); } } class Sinh implements Sach { @Override public void mangSachToiTruong() { System.out.println("Sach Sinh day roi"); } } public class TruongHoc { public static void main(String[] args) { Sach[] sach = { new Toan(), new Ly(), new Hoa(), new Sinh() }; for (Sach s : sach) { s.mangSachToiTruong(); } } }
2. Truyền nhận dữ liệu, thông điệp giữa các class
Người ta thường nói là các class trao đổi dữ liệu, thông điệp qua interface. Trong phần này chúng ta sẽ tìm hiểu nó. Vấn đề này sẽ được rõ ràng hơn nếu như chúng ta làm trên giao diện, tức là sử dụng các JFrame. Nhưng nếu bạn chưa làm với giao diện cũng không sao.
Bạn có 2 class là GiaoDien và Download. class GiaoDien có một nút, khi click vào nút thì gọi phương thức download nằm trong class Download để download file, và trong quá trình download, class download phải phản hồi lại cho class GiaoDien biết là download được bao nhiêu phần trăm rồi. Bạn hãy xem 2 cách làm dưới đây của mình nhé.
Cách 1 (Cách cùi bắp ngày xưa hay dùng): Truyền đối tượng Giao diện cho class Download để cập nhập phần trăm đã download.
package cachhoc.net.useinterface.senddata; public class GiaoDien { private Download download; /** * phuong thuc khoi tao Giao dien, dong thoi khoi tao doi tuong download va * truyen chinh giao dien sang class Download de cap nhat giao dien */ public GiaoDien() { download = new Download(this); } private void nhanDownload() { download.download(); } public void capNhatGiaoDien(int phanTramDownload) { System.out.println("class giao dien cap nhat duoc: " + phanTramDownload + "%"); } public static void main(String[] args) { GiaoDien giaoDien = new GiaoDien(); giaoDien.nhanDownload(); } }
package cachhoc.net.useinterface.senddata; public class Download { private GiaoDien giaoDien; /** * @param giaoDien * de cap nhat giao dien */ public Download(GiaoDien giaoDien) { this.giaoDien = giaoDien; } public void download() { for (int i = 0; i < 100; i++) { System.out.println("Dang download..."); giaoDien.capNhatGiaoDien(i); } System.out.println("Ket thuc download"); } }
Như các bạn đã thấy thì việc cập nhật giao diện tỏ ra đơn giản khi mà chúng ta chỉ cần truyền chính đối tượng giao diện sang class Download là xong.
Tuy nhiên nhiều vấn đề xảy ra khi mà đối tượng giaoDien được truyền sang, class download được phép gọi một số phương thức của class GiaoDien mà nó không cần, phương thức cập nhật giao diện bắt buộc phải cho phép gọi ở class Download thì mới có thể gọi được mà ở đây mình để là public chứ không thể là private. Hoặc giả sử như class chúng ta phải thông qua một class X nào đó ở giữa thì phương thức download của class Download mới được gọi đến và như vậy chúng ta phải truyền GiaoDien sang X, rồi X truyền sang Download, như vậy rất phức tạp… và rất nhiều hạn chế khác mà khi bạn làm thực tế sẽ hiểu rõ hơn.
Cách 2: Sử dụng Interface
Chúng ta tạo thêm một interface tên là CapNhat có chứa phương thức capNhatGiaoDien(); sau đó cho GiaoDien implements CapNhat như sau:
package cachhoc.net.useinterface.senddata; public interface CapNhat { public void capNhatGiaoDien(int giaTri); }
package cachhoc.net.useinterface.senddata; public class Download { private CapNhat capNhat; /** * @param capNhat * lang nghe su kien download */ public void addDownloadListener(CapNhat capNhat) { this.capNhat = capNhat; } public void download() { for (int i = 0; i < 100; i++) { System.out.println("Dang download..."); /** * Khi goi capNhat.capNhatGiaoDien(i); thi phuong thuc nay se tu * dong duoc goi trong giao dien * */ capNhat.capNhatGiaoDien(i); } System.out.println("Ket thuc download"); } }
package cachhoc.net.useinterface.senddata; public class GiaoDien implements CapNhat { private Download download; /** * phuong thuc khoi tao Giao dien, dong thoi dat lang nghe su kien download */ public GiaoDien() { download = new Download(); /** dat lang nghe su kien download */ download.addDownloadListener(this); } private void nhanDownload() { download.download(); } /** * Tu dong duoc goi trong qua trinh download thong qua interface CapNhap */ @Override public void capNhatGiaoDien(int giaTri) { System.out.println("class giao dien cap nhat duoc: " + giaTri + "%"); } public static void main(String[] args) { GiaoDien giaoDien = new GiaoDien(); giaoDien.nhanDownload(); } }
Các bạn có thể thấy rằng cách 2 này khắc phục được các nhược điển của cách 1. Class download không hề cần truyền đối tượng GiaoDien mà chỉ cần một interface CapNhat, do đó các phương thức, thuốc tính của GiaoDien không “bị lộ” hay có thể được gọi bên Download. Nếu phải thông qua một class X nào đó rồi download mới được thực hiện thì cũng không cần truyền đối tượng GiaoDien qua những class trung gian đó mà vẫn cập nhật được giao diện trong quá trình download. Hoặc giả sử có nhiều giao diện cần được cập nhật trong quá trình download thì đó là điều vô tư khi mà với cách 1 thì sẽ khó khăn hơn nhiều vì cần truyền mọi giao diện đó vào class Download…
Trên đây là 2 ứng dụng sử dụng Interface mà mình thấy rõ nhất. Rất mong nó có thể giúp ích gì đó cho bạn trong quá trình tìm hiểu cách dùng interface.
Nguồn bài viết tại cachhoc.net
Tự nhiên sáng ra không hiểu cách sử dụng interface, lên mạng search thế là rớt vô blog của thằng ku.
Bài viết khá hay. Và nhưng thông điệp của em: “nếu bạn có thể bổ xung điều gì đó thì thực sự là tuyệt với đối với mình cũng như bạn đọc theo dõi”, anh sẽ góp ý 1 số chỗ thế này.
1. Sử dụng Interface tạo hành động chung cho đối tượng: OK, mình có thể sử dụng interace ở đây, nhưng vẫn có thể sử dụng abstract class nữa. Vậy vì sao em lại dùng interface thay vì abstract class?
2. Truyền nhận dữ liệu, thông điệp giữa các class: excellent. Nhìn hao hao giống observer pattern, em đã làm được 1/3. Tuy nhiên anh góp thêm 2 ý để cho nó được 3/3 = 1 observer pattern hoàn chỉnh.
2.1. Idea của em good khi để GiaoDien implements CapNhat, và lớp Download chỉ làm việc với lớp CapNhat. Nhưng lớp GiaoDien lại làm việc trực tiếp với lớp Download? Tại sao mình không làm tương tự cho Download, nghĩa là Download sẽ implement 1 InterfaceX nào đó, và GiaoDien sẽ làm việc với InterfaceX. Lý do thì cũng tương tự như GiaoDien implements CapNhat.
2.2. Làm thế nào nếu muốn lớp Download notify cho nhiều hơn 1 đối tượng CapNhat? Thay vì dùng 1 đối tượng capNhat thì em có thể dùng 1 Collection chẳng hạn.
OK, vậy thôi. 🙂
Dạ vâng, em cảm ơn những ý kiến đóng góp của anh Hoàng. Em sẽ bổ xung vào bài viết.
Còn về các câu hỏi của anh thì…
1. Em nghĩ abstract class hoàn toàn có thể làm được việc này, tuy nhiên thì do 1 class chỉ kế thừa được 1 class nên nếu A & B có hành động chung là Hab, B & C có hành động chung là Hbc, thế thì việc này sẽ khó… mà khi dùng interface sẽ ổn 😉
2.1 Em nghĩ cái anh đưa ra rất tốt và hay.
2.2 Dùng Collection trong trường hợp này là đúng là tiện nhất anh nhỉ 😉
Anh có thể viết lại 2 góp ý này được không ạ
cái interface phải đặt riêng 1 file khác à bạn? mình để chung mà có public nó báo lỗi.
để chung cũng được mà. Nếu interface đơn giản đề chung cho tiện còn không thì để riêng ra cho dễ quản lý.
Em đang rất cần tất cả các bài hướng dẫn của anh về OOP nhưng sau em chỉ thấy có 2 phần là lớp , đối tượng và interface. Mong anh có thể hướng dẫn hết mấy phần còn lại!
Chào bạn. Hiện tại mình mới viết đl 2 bài đó thôi… các phần khác mình chưa có thời gian viết bạn ah.
Anh giải thích cho e tại sao interface có tính trừu tượng hóa 100% mà abstract class lại từ 0-100% được không ạ
Vì interface chỉ có hàm định nghĩa còn abstract thì có thể có hàm được triển khai
a có thể giải thích ý nghĩa của cái vòng for trong hàm main của bài mangSachToiTruong giúp e được không ạ?
for (Sach s : sach) {
s.mangSachToiTruong();
}
Đó là foreach nhé. Duyệt danh sách sach, mỗi lần duyệt là phần tử s.
Cảm ơn bạn, bài viết rất hữu ích!
Em chào anh!
Trong phần 2: Truyền dữ liệu giữa các class, không biết anh có thể giải thích rõ hơn về điểm khác nhau giữa 2 cách không ạ? Tại em thấy 2 cách cho dù có interfaceface hay không thì phần capnhat(giaoDien) vẫn có thể sử dụng như nhau, vậy điểm khác nhau là chỗ nào ạ?
Cảm ơn anh!
Tuy nhiên nhiều vấn đề xảy ra khi mà đối tượng giaoDien được truyền sang, class download được phép gọi một số phương thức của class GiaoDien mà nó không cần, phương thức cập nhật giao diện bắt buộc phải cho phép gọi ở class Download thì mới có thể gọi được mà ở đây mình để là public chứ không thể là private. Hoặc giả sử như class chúng ta phải thông qua một class X nào đó ở giữa thì phương thức download của class Download mới được gọi đến và như vậy chúng ta phải truyền GiaoDien sang X, rồi X truyền sang Download, như vậy rất phức tạp… và rất nhiều hạn chế khác mà khi bạn làm thực tế sẽ hiểu rõ hơn.
Tks anh!
“Tuy nhiên nhiều vấn đề xảy ra khi mà đối tượng giaoDien được truyền sang, class download được phép gọi một số phương thức của class GiaoDien mà nó không cần, phương thức cập nhật giao diện bắt buộc phải cho phép gọi ở class Download thì mới có thể gọi được mà ở đây mình để là public chứ không thể là private. ” : Chỗ này em đã đọc đi đọc lại rồi vẫn không thấy điểm khác nhau giữa 2 cách, khi sử dụng interface capnhat thì tham chiếu capnhat này vẫn có thể gọi các phương thức trong giaodien y hệt cách 1, vẫn có thể gọi những phương thức không cần thiết :(.
Ở class Download bạn có thể gọi hàm nhanDownload() ?. Nếu ở class GiaoDien có 1 biến public abc, bạn có gọi được trực tiếp biến abc đó ở class Download thông qua cái kia?
Xin lỗi đã làm phiền anh, em đã tìm ra chỗ sai của mình rồi! Cảm ơn anh!
🙂 ko co chi 🙂
ở cái vd thứ 2 công dụng của interface có phải là : cái đối tượng capnhat tại class dowload nó có khả năng tham chiếu tới bất kì đối tượng của lớp nào mà implment nó (kiểu như đối tượng capnhat đại diện cho tất cả đối tượng của lớp nào implement nó , và sau này mình muốn 1 class nào đó làm các hành động như trong class dowload thì mình chỉ cần implement capnhat thôi phải ko ạ ) , và trong interface đó khai báo method nào thì đối tượng capnhat mới gọi được đt đó phải ko ạ ??
Có thể nói như vậy
Bài của anh hay quá,nếu có thể mong anh thêm nhiều ví dụ hơn về chủ đề này được không anh,
Cảm ơn bạn, mình sẽ cố gắng 🙂
Công dụng của interface còn gì nữa không anh? Mặc dù đã biết sơ qua về 2 công dụng trên nhưng xem 2 ví dụ của anh làm em thêm rõ hơn .Thank anh
Bạn có thể cho mình biết, khi nào dùng interface, khi nào dùng abstract
Nôn na là dùng abstract khi mà 1 class nào đó có những phương thức có thể viết cụ thể ra và cũng có những phương thức ko viết cụ thể ra. Interface dùng khi tất cả các phương thức đều không viết cụ thể ra.
Cho em hỏi là làm như thế nào để cho một lớp kế thừa thông tin của 2 lớp khác nhau ạ? Em có tra và thấy bảo sử dụng interface ạ?
Ukm, trong java không có kế thừa 2 class bình thường. mà muốn kế thừa từ 2 trở lên thì dùng interface.