Chào mừng các bạn đã đến với bài học Java số 33, bài học về interface. Đây là bài học trong chuỗi bài về lập trình ngôn ngữ Java của Yellow Code Books.
Vâng, hôm nay chúng ta sẽ nói về interface. Nhưng bạn hãy dừng việc tưởng tượng rằng chúng ta đang nói đến việc làm sao xây dựng một giao diện cho ứng dụng Java. Tuy interface có nghĩa là giao diện, nhưng giao diện mà bạn đang nghĩ tới thì người ta gọi là UI cơ.
Vậy interface là gì nếu nó không phải là giao diện? Mời bạn đọc tiếp bài học hôm nay. Trước hết, để có thể hiểu bài viết về interface này, bạn nhất định phải đọc và hiểu khái niệm trừu tượng (abstraction) là gì trước.
Vì kiến thức về interface khá nhiều, mình cũng không chắc có thể nói hết tất cả các vấn đề của interface vào một bài viết hay không. Mình sẽ cố gắng trình bày nhiều nhất và rõ ràng nhất có thể, để bạn nắm được khái niệm và cách khai báo, sau này khi bạn bước qua lập trình ứng dụng Android, bạn sẽ hiểu rõ hơn về công dụng thực tế của interface.
Interface Là Gì?
Như mình có nói, interface không thể hiểu là giao diện được, đó là nghĩa tiếng Việt, còn với tiếng Anh thì interface cũng không phải là UI.
Interface thực ra là cái gì đó ở giữa hệ thống và các đối tượng bên ngoài hệ thống đó. Nó định nghĩa ra các hành động, để giúp kết nối giữa các đối tượng bên ngoài vào hệ thống. Bạn cứ tưởng tượng cái ti vi nhà bạn là một hệ thống, thì các nút nhấn của ti vi chính là interface mà chúng ta đang nói tới. Các nút này định nghĩa sẵn các hành động tác động đến hệ thống nếu người dùng nhấn vào các nút trên đó, như hành động tắt, mở, chuyển kênh,… Hệ thống tivi sẽ phải làm việc cật lực (chứ không phải interface, interface chỉ định nghĩa một “giao diện” tương tác thôi) để đáp ứng lại các hành động tương tác từ người dùng.
Quay lại OOP, giả sử nếu bạn có một hệ thống tính lương nhân viên hoàn chỉnh. Thì các tổ chức định nghĩa sẵn các hành động, giúp cho người quản trị có thể tương tác với hệ thống, hoặc giúp cho hệ thống tương tác với nhau. Các tổ chức đó chính là các interface.
Vậy đó, tổng hợp lại bạn có thể hiểu rằng interface chính là nơi nhận tương tác từ một đối tượng đến các đối tượng đang lắng nghe sự tương tác đó. Ngoài ra interface còn giúp định nghĩa sẵn các hành động, để các đối tượng bên ngoài biết đường tương tác, và các đối tượng bên trong hệ thống sẵn sàng đáp ứng các tương tác đó thông qua việc hiện thực sẵn các hành động đã được định nghĩa đó.
Đến đây thì bạn đã hườm hườm hiểu nghĩa và công dụng của interface rồi ha. Chúng ta sẽ từng bước đi sâu vào interface ở các mục bên dưới.
Khai Báo Interface Như Thế Nào?
Chưa cần lắm sự hiểu biết tường tận interface. Mình mời bạn xem qua cú pháp để khai báo một interface trước.
interface tên_interface { các_thuộc_tính; các_phương_thức; }
Mời bạn so sánh giữa cú pháp khai báo một interface của bài hôm nay với cú pháp khai báo một lớp ở bài trước đó này đây, để dễ dàng cho cả hai khi mình nói đến ý sau đây.
Qua cú pháp trên, bạn có thể thấy interface hơi giống với lớp bình thường mà bạn biết. Bạn xem, chỉ khác với lớp ở chỗ, khi khai báo một lớp thì bạn dùng từ khóa class, còn khi khai báo interface thì bạn dùng từ khóa interface.
Một lát nữa đây khi tiến hành khai báo cụ thể một interface, bạn còn bắt gặp interface rất giống với một lớp trừu tượng (abstract class) ở chỗ, nó không thể được dùng để tạo ra các đối tượng như những lớp bình thường khác. Nhiều tài liệu còn gọi interface là trừu tượng trăm phần trăm (absolute abstraction), bởi vì interface chỉ chứa các phương thức trừu tượng (chính là các phương thức không có thân hàm), trong khi lớp trừu tượng có thể chứa các phương thức trừu tượng lẫn các phương thức không trừu tượng bên trong nó, bạn có thể xem lại ý này qua bài học hôm trước nhé.
Đến đây thì bạn đã hiểu rõ hơn những khái niệm của interface mà mình cố gắng diễn đạt trên kia.
– Nói interface là một nơi chứa các hành động để nhận sự tương tác. Vâng, chính các phương thức trừu tượng của nó chính là các hành động.
– Nói interface giúp các đối tượng bên ngoài tương tác với hệ thống. Vì interface định nghĩa sẵn các phương thức trừu tượng, nó buộc các đối tượng bên trong hệ thống khi triển khai các interface này, buộc phải hiện thực các phương thức (hay cũng có thể hiểu là các hành động). Chính việc ràng buộc này sẽ rất dễ cho các đối tượng bên ngoài hiểu và tương tác vào.
Nếu bạn vẫn chưa hiểu lắm, thì hãy cùng mình khai báo một interface như thế nào nhé.
Thực Hành Khai Báo Một Interface
Chúng ta cùng nhau quay lại với project về tính chu vi, diện tích, thể tích của hình học mà bạn đã làm quen từ bài học số 21. Nếu như ở bài học đó, bạn xây dựng lớp HinhHoc là lớp cha nhất, lớp này chứa đựng các thuộc tính và các phương thức dùng chung cho các lớp con của nó. Bạn có thể nâng cấp lớp HinhHoc này thành một lớp trừu tượng, để có nhiều hơn ràng buộc phải hiện thực các phương thức trừu tượng cho lớp con. Nhưng với ví dụ này đây, mình xem HinhHoc là một interface, để có một ràng buộc trừu tượng trọn vẹn. Bạn xem khai báo một interface như sau.
package shapes; interface HinhHoc { float PI = 3.14f; float tinhChuVi(); float tinhDienTich(); float tinhTheTich(); void xuatThongTin(); }
Các Đặc Tính Của Interface
Nào, trước khi sử dụng interface HinhHoc mà bạn vừa khai báo ở ví dụ trên, chúng ta cùng nhau điểm qua một vài đặc tính của interface.
– Đầu tiên, như bạn thấy ở cú pháp và ví dụ trên, để khai báo một interface, bạn phải dùng từ khóa interface thay vì từ khóa class như khi dùng với các lớp mà bạn biết.
– Tiếp theo, như mình có nói, interface không thể khai báo các đối tượng của nó, giống như lớp trừu tượng vậy. Không tin à, bạn thử xem.
– Và mình cũng đã nói, interface chính là lớp trừu tượng trăm phần trăm. Vì các phương thức bên trong interface phải đều là các phương thức trừu tượng, dù cho bạn không hề khai báo bất kỳ từ khóa abstract nào cho các phương phương thức bên trong interface này cả, bạn có thể đọc thêm ý này bên dưới đây.
– Interface không hề có constructor. Đặc tính này khác với lớp trừu tượng, lớp trừu tượng cho phép có constructor, mặc dù chúng ta không thể tạo ra đối tượng từ constructor này của lớp trừu tượng, nhưng constructor của nó vẫn được dùng để khởi tạo các giá trị khi các lớp con của nó gọi đến. Bạn sẽ thấy điều này ở bài thực hành dưới đây.
– Mặc định thì các thuộc tính bên trong interface sẽ là public, static và final. Đó là lý do vì sao thuộc tính PI ở khai báo trên kia không cần dùng đến các từ khóa này.
– Mặc định thì các phương thức bên trong interface sẽ là abstract và public. Do đó sẽ không có phương thức nào trong interface được phép có thân hàm. Bạn cũng có thể kiểm chứng lại khai báo ở bài thực hành trên, tuy bạn không khai báo chúng là trừu tượng, nhưng mặc định chúng là vậy, nếu bạn thử khai báo thân hàm cho chúng, bạn sẽ nhận được thông báo lỗi đấy.
– Để khai báo một lớp “con” của interface, chính là lớp sẽ hiện thực (implement) các phương thức trừu tượng của nó, chúng ta dùng từ khóa implements thay vì extends như bạn đã biết. Bạn sẽ hiểu rõ hơn ý này khi xem bài thực hành dưới đây.
Thực Hành Hiện Thực Các Lớp Con Của Interface
Nói là lớp con của interface cũng không hẳn là đúng, chính xác nhất chúng ta nên nói đây là các lớp triển khai của interface thì hay hơn. Dưới đây là một vài lớp triển khai từ interface HinhHoc trên kia.
Trước hết, chúng ta sẽ xây dựng lớp HinhTron. Để HinhTron là lớp triển khai của HinhHoc, như đã nói, chúng ta sẽ dùng từ khóa implements thay cho extends. Và cũng như bạn đã từng thực hành với lớp trừu tượng, khi bạn triển khai từ một interface nào đó, sẽ có một báo lỗi bằng một icon hình bóng đèn bên cạnh dấu chéo màu đỏ như thế này.
Và cũng tương tự như bài thực hành trước, bạn hoàn toàn dễ dàng nhờ hệ thống tự tạo ra các phương thức override lại các phương thức trừu tượng từ HinhHoc giúp bạn.
Việc của bạn là hoàn chỉnh lớp HinhTron dựa trên các phương thức được override “tạm bợ” bởi hệ thống qua bước trên.
package shapes; public class HinhTron implements HinhHoc { protected String ten; protected float banKinh; // Constructor public HinhTron(float banKinh) { this.ten = "Hình Tròn"; this.banKinh = banKinh; } @Override public float tinhChuVi() { return 2 * PI * banKinh; } @Override public float tinhDienTich() { return PI * banKinh * banKinh; } @Override public float tinhTheTich() { // Do HinhTron không có tính thể tích, nên phương thức này chỉ override từ HinhHoc mà không làm gì cả return 0; } @Override public void xuatThongTin() { System.out.println(ten); System.out.println("Chu vi: " + tinhChuVi()); System.out.println("Diện tích: " + tinhDienTich()); } }
Đến đây, nếu bạn xây dựng thêm lớp HinhTru kế thừa từ HinhTron thì sao? Lúc bấy giờ sự ràng buộc từ các phương thức trừu tượng của ông nội HinhHoc với cháu HinhTru đã hết. Nếu có ràng buộc thì chỉ là đòi hỏi bạn phải khai báo một constructor cho HinhTru để khởi tạo contructor của cha nó mà thôi.
Nếu bạn vẫn muốn sự ràng buộc với các phương thức của HinhHoc? Ok, bạn có thể khai báo HinhTron là lớp trừu tượng. Lưu ý là việc thay đổi HinhTron thành lớp trừu tượng chỉ để bạn tham khảo thôi nhé, vì nếu làm vậy bạn sẽ mất cơ hội khởi tạo đối tượng cho nó đấy.
package shapes; public abstract class HinhTron implements HinhHoc { protected String ten; protected float banKinh; // Constructor public HinhTron(float banKinh) { this.ten = "Hình Tròn"; this.banKinh = banKinh; } @Override public float tinhChuVi() { return 2 * PI * banKinh; } @Override public float tinhDienTich() { return PI * banKinh * banKinh; } @Override public abstract float tinhTheTich(); @Override public void xuatThongTin() { System.out.println(ten); System.out.println("Chu vi: " + tinhChuVi()); System.out.println("Diện tích: " + tinhDienTich()); } }
Có một điều hay ho rằng, với code trên của lớp trừu tượng HinhTron, bạn hoàn toàn có thể xóa bỏ khai báo phương thức tinhTheTich() đi. Thật vậy, khi mà HinhTron đã trở thành một lớp trừu tượng, thì nó không nhất thiết phải hiện thực tất cả các phương thức trừu tượng từ interface HinhHoc nữa, vì hệ thống biết rằng con của nó rồi sẽ phải hiện thực các phương thức trừu tượng còn lại này. Bạn thử xem nhé.
Vậy là với thay đổi từ code trên, HinhTru sẽ buộc phải hiện thực phương thức trừu tượng tinhTheTich(). Còn phương thức xuatThongTin() mình chủ động override lại để xuất thêm thông tin thể tích mà thôi.
package shapes; public class HinhTru extends HinhTron { protected float chieuCao; protected float theTich; public HinhTru(float banKinh, float chieuCao) { super(banKinh); this.ten = "Hình Trụ"; this.chieuCao = chieuCao; } @Override public float tinhTheTich() { return tinhDienTich() * chieuCao; } @Override public void xuatThongTin() { super.xuatThongTin(); System.out.println("Thể tích: " + tinhTheTich()); } }
Các Đặc Tính Của Các Lớp Triển Khai Interface
Trên kia bạn đã biết các đặc tính của một interface, vậy còn với các lớp triển khai từ interface đó thì sao, chúng có những đặc trưng và đặc quyền gì.
– Bạn nên biết, một lớp có thể triển khai nhiều interface một lúc. Các interface được triển khai vẫn được khai báo sau từ khóa implements, nhưng cách nhau bởi dấu phẩy (,). Điều này khác với tính chất của kế thừa, khi mà một lớp chỉ được kế thừa đến một lớp cha mà thôi, nếu quên thì bạn xem lại bài 21 nhé. Ví dụ như, bạn có thể khai báo một lớp HinhTru triển khai từ nhiều interface như sau public class HinhTru extends HinhTron implements InterfaceA, InterfaceB.
– Như bài thực hành trên bạn có thể thấy nếu một lớp triển khai từ interface mà là lớp trừu tượng, thì lớp đó không còn buộc phải hiện thực các phương thức trừu tượng từ interface đó.
Mình biết kiến thức về interface khá rối. Nhưng bạn cũng nên biết tại sao phải có interface.
Tại Sao Phải Dùng Interface?
Mình hi vọng ở ý cuối cùng này sẽ giúp bạn hiểu hết về interface. Còn việc thực sự sử dụng interface như thế nào, thì mình khuyên bạn nên đọc nhiều code từ nhiều nguồn tài liệu khác, hoặc bắt đầu học lập trình ứng dụng Android, bạn sẽ biết rõ về interface hơn đấy.
– Đầu tiên bạn có thể thấy rằng, interface giống với lớp trừu tượng ở chỗ nó giúp bạn tạo ra ràng buộc đối với các đối tượng bên trong hệ thống. Và nếu bạn cần một ràng buộc toàn vẹn, thì interface là một giải pháp tốt.
– Bạn có thể tận dụng tính chất một lớp có thể triển khai nhiều interface trên kia để mở rộng hơn cho một số mục đích kế thừa.
– Và cũng với ý mà mình nói từ đầu bài học, interface là một giao diện nhận tương tác từ bên ngoài, nên nếu bạn có xây dựng UI cho ứng dụng, hoặc bạn xây dựng các ứng dụng Android bằng Java như mình có nhắc đến, thì interface sẽ được tận dụng để xây dựng các sự kiện tương tác giữa người dùng và hệ thống, như sự kiện nhấn lên Button, sự kiện nhấn chọn một item list,..
Chúng ta vừa xem xong kiến thức về interface. Bạn có thấy kiến thức này khó hiểu không? Nếu có bất cứ thắng mắc gì thì để lại bình luận bên dưới bài học hôm nay cho mình nhé.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao bên dưới mỗi bài nếu thấy thích.
– Comment bên dưới mỗi bài nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
Bài Kế Tiếp
Chúng ta sẽ xem xét đến lớp lồng vào nhau thì như thế nào, hay còn gọi với cái tên lớp trong lớp, hay inner class.
Anh có đề cập interfaces sử dụng rất tốt trên UI và xây dựng app Android. Anh có thể link bài viết để em có thể tham khảo thêm công dụng của interfaces trên android được không
Dễ tìm lắm bạn, mình ví dụ như bài học về Fragment này: https://yellowcodebooks.com/2017/11/30/android-bai-31-tiep-tuc-lam-quen-voi-fragment/. Một tình huống cụ thể trong bài học là khi xây dựng FirstFragment, chúng ta xây dựng interface có tên OnFirstFragmentListener, interface này định nghĩa sẵn các hành động. FirstFragment ban đầu chưa biết ai sẽ implement hành động này, nhưng cứ gọi đến nó, vì nó là các “nút điều khiển mà”. Để rồi sang bài học tiếp theo của link trên kia, lớp MainActivity sẽ implement interface này để hiện thực các hành động mà interface đã định nghĩa ra, và được FirstFragment gọi đến.
Rất hay và rất bổ ích, xin cảm ơn!
Em chào anh,
Em có đọc qua bài của anh và có câu hỏi nhỏ.
Trong bài trên anh có viết là:
“Có một điều hay ho rằng, với code trên của lớp trừu tượng HinhTron, bạn hoàn toàn có thể xóa bỏ khai báo phương thức tinhTheTich() đi. Thật vậy, khi mà HinhTron đã trở thành một lớp trừu tượng, thì nó không nhất thiết phải hiện thực tất cả các phương thức trừu tượng từ interface HinhHoc nữa, vì hệ thống biết rằng con của nó rồi sẽ phải hiện thực các phương thức trừu tượng còn lại này. Bạn thử xem nhé”.
Em thử rồi nhưng khi khai báo một class khác để extends class trừu tượng HinhTron và viết lại thân hàm cho phương thức tinhTheTich(), compiler báo lỗi. Không biết em có hiểu sai ý anh không khi làm như vậy không ạ?
Em cảm ơn.
Chào bạn, có lẽ nhờ bạn chat qua Facebook hoặc gởi email cho mình kèm khai báo lớo HinhTron và lớp extends từ HinhTron mà bạn viết cho mình xem với nhé, khi đó mình mới biết chính xác bạn có khai báo đúng hay không.
chào ad, cho mình hỏi nếu lớp HinhHoc là lớp Abstract, thì lớp này ko thể tạo ra đối tượng được, như vậy nó cũng mất luôn Tính Đa hình rồi nhỉ?
mình cảm ơn ah!
Chào bạn, đây là câu hỏi hay, mình đã không nói đến trong bài học. Câu trả lời là, dù một lớp (HinhHoc chẳng hạn) là một Abstract hay Interface, thì tính Đa hình vẫn nguyên vẹn đó bạn.
Khi đó chúng ta không tạo ra một đối tượng HinhHoc, nhưng chúng ta có thể khai báo một biến HinhHoc rồi khởi tạo các đối tượng con của nó. Như đoạn code sau.
HinhHoc hinhHoc1 = new HinhTron();
hinhHoc1.tinhDienTich();
HinhHoc hinhHoc2 = new HinhChuNhat();
hinhHoc2.tinhDienTich();
cho em hỏi v tính đa hình của HinhHoc khi nó là interface được thể hiện như thế nào v ạ ?
Bài viết quá hay và bổ ích ạ ! Cảm ơn admin nhiều lắm <3
Cho mình hỏi, ví dụ mình có 3 class là OngNoi,Cha và Con thì tính đa hình vẫn được thể hiện ở cả lớp OngNoi khi nó dùng để new ra đối tượng thể hiện của lớp Con đúng k ạ ?
Ví dụ : OngNoi ongNoi = new Con();
Hay quá anh