Java: Interface (Giao diện)
Giới thiệu
Java không hỗ trợ đa thừa kế. Tuy nhiên, có một số trường hợp khi nó trở thành bắt buộc đối với một đối tượng để thừa kế các thuộc tính từ nhiều lớp để tránh sự dư thừa và phức tạp trong mã lệnh. Với mục đích đó, Java cung cấp một cách giải quyết là sử dụng giao diện hay giao tiếp (interface).
Một interface trong Java là một hợp đồng quy định các chuẩn được theo sau bởi các kiểu thực thi nó. Các lớp mà chấp nhận hợp đồng thì phải tuân thủ nó.
Interface và lớp có một số điểm tương đồng nhau như sau:
- Interface có thể chứa nhiều phương thức.
- Một interface được lưu thành tập tin với đuôi mở rộng là .java và tên của tập tin phải trùng với tên của interface.
- Bytecode của một interface cũng được lưu thành một tập tin có đuôi mở rộng là .class.
- Các giao diện được lưu trong các gói và tập tin bytecode được lưu trong cấu trúc thư mục trùng tên với tên của gói.
Tuy nhiên, một interface và một lớp cũng có những điểm khác nhau như sau:
- Một interface thì không thể có thể hiện.
- Interface không thể có hàm tạo.
- Tất cả các phương thức của interface đều ngầm định là trừu tượng (abstract).
- Các trường được khai báo trong một interface phải bao gồm cả static và final. Interface không thể có các trường thể hiện.
- Một interface không được thừa kế nhưng được thực thi bởi một hoặc nhiều lớp.
- Một nterface có thể thừa kế từ nhiều interface khác.
Mục đích của interface
Các đối tượng trong Java tương tác với thế giới bên ngoài với sự trợ giúp của các phương thức để tiếp xúc chúng. Vì vậy, có thể nói rằng, các phương thức phục vụ như là những giao diện của đối tượng với thế giới bên ngoài.
Điều này tương tự như những nút phía trước một chiếc tivi. Những nút này đóng vai trò như là một giao diện giữa người dùng và vi mạch điện tử và hệ thống dây điện ở phía bên kia của vỏ nhựa. Khi ta nhấn nút "Power", thì tivi sẽ được bật hoặc tắt.
Trong Java, một giao diện là một tập các phương thức quan hệ mà không có phần thân. Những phương thức này tạo thành các hợp đồng mà các lớp thực hiện phải đồng ý. Khi một lớp thực thi một giao diện thì nó trở thành chính thức hơn về hành vi nó hứa hẹn sẽ cung cấp. Hợp đồng này được thực thi tại thời điểm xây dựng bởi trình biên dịch. Nếu một lớp thực thi một giao diện, thì tất cả các phương thức được khai báo bởi giao diện đó phải xuất hiện trong lớp thực thi để việc biên dịch được thành công.
Vậy nên, trong Java, một giao diện là một kiểu tham chiếu mà tương tự như một lớp. Tuy nhiên, nó chỉ có thể chưa những chữ ký phương thức, hằng, và các kiểu lồng nhau. Không được định nghĩa phương thức mà chỉ được khai báo. Ngoài ra, không giống với lớp, các giao diện không thể được thể hiện và phải được thực thi bởi các lớp hoặc được thừa kế bởi những giao diện khác để sử dụng chúng.
Có một vài tính huống trong kỹ thuật phần mềm khi nó trở nên cần thiết cho những nhóm các nhà phát triển khác nhau phải đồng ý với một 'hợp đồng' để xác định cách tương tác với phần mềm của họ. Tuy nhiên, mỗi nhóm phải có sự tự do để viết mã lệnh của họ theo cách mà họ mong muốn mà không cần biết cách các nhóm khác đang viết mã lệnh thế nào. Các giao diện của Java có thể được sử dụng để định nghĩa những hợp đồng như vậy.
Các giao diện không thuộc về bất kỳ một hệ thống phân cấp lớp nào, mặc dù chúng hoạt động chung với các lớp. Java không hỗ trợ đa thừa kế, do vậy các giao diện được cung như một sự thay thế cho điều này. Trong Java, một lớp chỉ có thể thừa kế từ một lớp khác nhưng có thể thực thi nhiều giao diện. Do đó, các đối tượng của một lớp có thể có nhiều kiểu, chẳng hạn như kiểu của bản thân lớp của chúng hoặc là các kiểu giao diện mà các lớp thực thi. Cú pháp khai báo một giao diện là như sau:
{
// khai báo các hằng
// khai báo các phương thức trừu tượng
}
Ví dụ,
static final int someInteger;
public void someMethod();
}
Trong Java, tên giao diện được viết theo dang chữ lạc đà (CamelCase), đó là ký tự đầu của mỗi từ được viết hoa. Ngoài ra, tên của giao diện một tả một hoạt động mà lớp có thể giải quyết. Ví dụ,
interface Enumerable
interface Comparable
Một số lập trình viên lại thích đặt tiền tố 'I' ở đầu tên của giao diện để phân biệt giao diện với lớp. Ví dụ,
interface IEnumerable
interface IComparable
Lưu ý là việc khai báo phương thức là không có bất kỳ cặp ngoặc xoắn ({}) nào và phải kết thúc là dấu chấm phẩy (;). Ngoài ra, thân của giao diện chỉ chứa những phương thức trừu tượng và không có phương thức cụ thể. Tuy nhiên, vì tất cả các phương thức trong giao diện ngầm định là trừu tượng, nên ta không sử dụng từ khóa 'abstract' khi khao báo các phương thức của giao diện.
Khi một lớp thực thi một giao diện, nó phải thực thi tất cả các phương thức. Nếu lớp không thực thi tất cả các phương thức thì nó phải được đánh dấu là trừu tượng. Ngoài ra, nếu lớp thực thi giao diện là lớp trừu tượng, thì một trong những lớp con của nó phải thực thi những phương thức chưa được thực thi. Và nếu bất kỳ một lớp con nào của lớp trừu tượng không thực thi tất cả các phương thức của giao diện, thì lớp con đó cũng phải được đánh dấu là trừu tượng.
Các thành phần dữ liệu của giao diện ngầm định là dạng static, final, và public.
Ta xét một hệ thống phân cấp của các xe mà trong đó IVehicle là một giao diện và khai báo các phương thức cho các lớp thực thi như là TwoWheeler, FourWheeler, ... có thể thực thi. Để tạo một giao diện mới trong IDE NetBeans, ta kích phải chuột lên tên của gói và chọn New → Java Interface như thể hiện ở hình 11.1.
Một hộp thoại xuất hiện trong đó người dùng phải cung cấp tên cho giao diện, rồi nhấn nút OK. Điều này sẽ tạo một giao diện với tên cụ thể.
Đoạn mã 1 định nghĩa giao diện IVehicle.
Đoạn mã 1:
// khai báo và khởi tạo hằng
static final String STATEID=”LA-09”; // variable to store state ID
/**
* phương thức trừu tượng khởi động xe
*/
public void start();
/**
* phương thức trừu tượng tăng tốc xe
*/
public void accelerate(int speed);
/**
* phương thức trừu tượng hãm phanh
*/
public void brake();
/**
* phương thức trừu tượng dừng xe
*/
public void stop();
}
Đoạn mã 1 định nghĩa một giao diện IVehicle với một hằng chuỗi tĩnh là STATEID. Ngoài ra, nó khai báo các phương thức trừu tượng như là start(), accelerate(int), brake(), và stop(). Để sử dụng giao diện này, thì một lớp được yêu cầu để thực thi giao diện. Lớp thực thi giao diện phải thực thi tất cả các phương thức này.
Cú pháp để thực thi một giao diện là như sau:
// các thành phần lớp
// ghi đè các phương thức trừu tượng của giao diện
}
Đoạn mã 2 định nghĩa lớp TwoWheeler thực thi giao diện IVehicle ở đoạn mã 1.
Đoạn mã 2:
String ID; // biến lưu ID của xe
String type; // biên lưu kiểu xe
/**
* hàm tạo có tham số để khởi tạo các giá trị nhập vào từ người dùng
*/
public TwoWheeler(String ID, String type){
this.ID = ID;
this.type = type;
}
/**
* ghi đè các phương thức để khởi động xe
*/
@Override
public void start() {
System.out.println(“Starting the “+ type);
}
/**
* ghi đè phương thức để tăng tốc xe
*/
@Override
public void accelerate(int speed) {
System.out.println(“Accelerating at speed:”+speed+ “ kmph”);
}
/**
* ghi đè phương thức để hãm phanh
*/
@Override
public void brake() {
System.out.println(“Applying brakes”);
}
/**
* ghe đè phương thức để dừng xe
*/
@Override
public void stop() {
System.out.println(“Stopping the “+ type);
}
/**
* hiển thị thông số của xe
*/
public void displayDetails(){
System.out.println(“Vehicle No.: “+ STATEID+ “ “+ ID);
System.out.println(“Vehicle Type.: “+ type);
}
}
public class TestVehicle {
public static void main(String[] args){
// xác nhận số lượng đối số dòng lệnh
if(args.length==3) {
// tạo thể hiện cho lớp TwoWheeler
TwoWheeler objBike = new TwoWheeler(args[0], args[1]);
// gọi các phương thức
objBike.displayDetails();
objBike.start();
objBike.accelerate(Integer.parseInt(args[2]));
objBike.brake();
objBike.stop();
}
else {
System.out.println(“Usage: java TwoWheeler <ID> <Type> <Speed>”);
}
}
}
Đoạn mã 2 định nghĩa lớp TwoWheeler thực thi giao diện IVehicle. Lớp này bao gồm một số biến thể hiện và một hàm tạo để khởi tạo giá trị cho các biến. Lưu ý rằng lớp thực thi tất cả các phương thức của giao diện IVehicle. Phương thức displayDetails() được dùng để hiển thị chi tiết của phương tiện giao thông cụ thể.
Phương thức main() được định nghĩa trong một lớp khác có tên TestVehicle. Trong phương thức main(), số lượng các đối số dòng lệnh cụ thể được xác nhận và phù hợp với đối tượng của lớp TwoWheeler được tạo. Tiếp theo, đối tượng được sử dụng để gọi các phương thức khác nhau của lớp.
Hình sau đây cho thấy output của đoạn mã trên khi người dùng truyền đối số dòng lệnh là CS-2723 Bike 80.
Thực thi nhiều giao diện
Java không hỗ trợ đa thừa kế các lớp nhưng cho phép thực thi nhiều giao diện để mô phỏng đa thừa kế. Để thực thi nhiều giao diện, ta viết tên các giao diện phía sau từ khóa implements và phân cách nhau bằng dấu phay (,). Ví dụ,
}
Đoạn mã 3 định nghĩa giao diện IManufacturer.
Đoạn mã 3:
public interface IManufacturer {
/**
* phương thức trừu tượng để thêm thông tin liên hệ
*/
public void addContact(String detail);
/**
* phương thức trừu tượng để gọi nhà sản xuất
*/
public void callManufacturer(String phone);
/**
* phương thức để tiến hành thanh toán
*/
public void makePayment(float amount);
}
Giao diện IManufacturer khai báo các phương thức trừu tượng gồm addContact(String), callManufacturer(String), và makePayment(float) mà phải được định nghĩa bởi lớp thực thi.
Lớp đã sửa đổi, TwoWheeler thực thi cả giao diện IVehicle và IManufacturer được hiển thị trong đoạn mã 4.
Đoạn mã 4:
class TwoWheeler implements IVehicle, IManufacturer {
String ID; // ID xe
String type; // kiểu xe
public TwoWheeler(String ID, String type){
this.ID = ID;
this.type = type;
}
@Override
public void start() {
System.out.println(“Starting the “+ type);
}
@Override
public void accelerate(int speed) {
System.out.println(“Accelerating at speed:”+speed+ “ kmph”);
}
@Override
public void brake() {
System.out.println(“Applying brakes...”);
}
@Override
public void stop() {
System.out.println(“Stopping the “+ type);
}
public void displayDetails()
{
System.out.println(“Vehicle No.: “+ STATEID+ “ “+ ID);
System.out.println(“Vehicle Type.: “+ type);
}
// thực thi các phương thức của giao diện
@Override
public void addContact(String detail) {
System.out.println(“Manufacturer: “+detail);
}
@Override
public void callManufacturer(String phone) {
System.out.println(“Calling Manufacturer @: “+phone);
}
@Override
public void makePayment(float amount) {
System.out.println(“Payable Amount: $”+amount);
}
}
public class TestVehicle {
public static void main(String[] args){
// xác nhận đối số dòng lệnh
if(args.length==6) {
// thể hiện của lớp
TwoWheeler objBike = new TwoWheeler(args[0], args[1]);
objBike.displayDetails();
objBike.start();
objBike.accelerate(Integer.parseInt(args[2]));
objBike.brake();
objBike.stop();
objBike.addContact(args[3]);
objBike.callManufacturer(args[4]);
objBike.makePayment(Float.parseFloat(args[5]));
}
else{
// hiển thị thông báo lỗi
System.out.println(“Usage: java TwoWheeler <ID> <Type> <Speed>
<Manufacturer> <Phone> <Amount>”);
}
}
}
Lớp TwoWheeler bây giờ thực thi cả hai giao diện là IVehicle và IManufacturer, nghĩa là, nó thực thi tất cả các phương thức của cả hai giao diện.
Hình sau đây thể hiện output của đoạn mã đã sửa đổi ở trên, người dùng truyền đối số dòng lệnh là CS-2737 Bike 80 BN-Bikes 808-283-2828 300.
Lưu ý rằng giao diện IManufacturer chỉ có thể được thực thi bởi những lớp khác như FourWheeler, Furniture, Jewelry, và vân vân, mà yêu cầu thông tin nhà sản xuất.
Tìm hiểu khái niệm trừu tượng
Trừu tượng là một yếu tố thiết yếu của lập trình hướng đối tượng. Trong Java, nó được định nghĩa là quá trình ẩn đi những chi tiết không cần thiết và chỉ bộc lộ những tính năng thiết yếu của đối tượng cho người dùng. Trừu tượng là một khái niệm được sử dụng bởi các lớp trong đó bao gồm các thuộc tính và phương thức để thực hiện các hoạt động trên các thuộc tính này.
Tính trừu tượng cũng có thể được sử dụng thông qua các thành phần. Ví dụ, một lớp Vehicle bao gồm động cơ, lốp xe, khóa điện, và có thể gồm những thành phần khác. Để xây dựng lớp Vehicle, ta không cần phải biết về hoạt động bên trong của những thành phần khác nhau, mà chỉ cần biết làm thế nào để tương tác hoặt giao tiếp với chúng. Đó là, gửi và nhận tin nhắn đến và từ chúng cũng như làm cho các đối tượng khác nhau của lớp Vehicle tương tác với nhau.
Trong Java, các lớp trừu tượng và giao diện được dùng để thực thi khái niệm trừu tượng. Một lớp trừu tượng hoặc giao diện là không cụ thể, nói cách khác là chúng không hoàn chỉnh. Để sử dụng một lớp trừu tượng hoặc giao diện thì cần phải mở rộng hoặc thực thi các phương thức trừu tượng với các hành vi cụ thể theo bối cảnh trong đó nó được sử dụng. Tính trừu tượng được sử dụng để định nghĩa một đối tượng dựa trên các thuộc tính, chức năng, và giao diện của nó.
Sự khác nhau giữa lớp trừu tượng và giao diện được thể hiện ở bảng dưới đây.
Lớp trừu tượng | Giao diện |
---|---|
Một lớp trừu tượng có thể có cả các phương thức trừu tượng và phương thức cụ thể là những phương thức có phần thân. | Một giao diện chỉ có thể có các phương thức trừu tượng. |
Một lớp trừu tượng có thể có những biến không final. | Các biến được khai báo trong giao diện ngầm định là final. |
Một lớp trừu tượng có thể có những thành phần với các đặc tả truy cập khác nhau như private, protected, public. | Các thành phần của giao diện mặc định là public. |
Lớp trừu tượng được thừa kế sử dụng từ khóa extends. | Giao diện được thực thi sử dụng từ khóa implements. |
Lớp trừu tượng có thể thừa kế từ một lớp và đồng thời thực thi từ nhiều giao diện. | Một giao diện có thể mở rộng một hoặc nhiều giao diện khác. |
Trừu tượng và Đóng gói là hai khái niệm quan trọng và thiết yếu trong lập trình hướng đối tượng Java. Hai khái niệm này hoàn toàn khác nhau. Trừu tượng dùng để đưa ra hành vi từ 'Làm thế nào để chính xác' với sự thực thi, trong khi đó Đóng gói dùng để ẩn đi những chi tiết thực thi từ thế giới bên ngoài để đảm bảo rằng bất kỳ sự thay đổi nào tới lớp đều không ảnh hưởng đến những lớp phụ thuộc. Một số điểm khác nhau của hai khái niệm này là như sau:
- Trừu tượng được thực thi sử dụng một giao diện và một lớp trừu tượng, trong khi đó Đóng gói được thực thi bằng cách sử dụng các bổ từ truy cập gồm private, default hoặc package-private, và protected.
- Đóng gói cũng còn được gọi là che dấu dữ liệu.
- Cơ sở của nguyên tắc thiết kế 'programming for interface than implementation' là trừu tượng và 'encapsulate whatever changes' là đóng gói.