Lớp trừu tượng và giao diện
Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 38 trên 87.
Giờ đây, khi đã hiểu cả lớp trừu tượng lẫn giao diện, bạn có thể tự hỏi: khi nào nên dùng loại này thay vì loại kia? Lựa chọn phụ thuộc vào điều bạn muốn mô hình hóa.
Dùng lớp trừu tượng khi các lớp có chung một lớp cơ sở với trạng thái hoặc hành vi chung. Lớp trừu tượng có thể có biến instance, hàm khởi tạo và kết hợp các phương thức trừu tượng lẫn phương thức cụ thể:
public abstract class Animal {
protected String name; // trạng thái dùng chung
public Animal(String name) {
this.name = name;
}
public void sleep() { // hành vi dùng chung
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // phải triển khai
}Dùng một giao diện (interface) khi bạn muốn định nghĩa một khả năng mà các lớp không liên quan có thể cùng chia sẻ. Giao diện tập trung vào những gì một đối tượng có thể làm, chứ không phải nó là gì:
public interface Flyable {
void fly();
}
// Các lớp (class) không liên quan vẫn có thể chia sẻ khả năng này
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }Đây là phần so sánh nhanh:
| Tính năng | Lớp trừu tượng | Giao diện |
|---|---|---|
| Biến thực thể | Có | Chỉ hằng số |
| Hàm khởi tạo | Có | Không |
| Kế thừa nhiều lớp | Không (chỉ extends một lớp) | Có (implements nhiều giao diện) |
| Bộ điều chỉnh quyền truy cập | Bất kỳ | Chỉ public (đối với các phương thức trừu tượng) |
Một nguyên tắc thực tế: nếu bạn thấy mình đang tạo một lớp trừu tượng chỉ có các phương thức trừu tượng và không có trạng thái, thì có lẽ giao diện là lựa chọn phù hợp hơn.
Thử thách
DễHãy xây dựng một hệ thống phương tiện để minh họa khi nào nên dùng lớp trừu tượng thay vì giao diện. Bạn sẽ mô hình hóa các phương tiện có chung trạng thái và hành vi thông qua một lớp trừu tượng, đồng thời bổ sung các khả năng tùy chọn thông qua giao diện.
Bạn sẽ sắp xếp code trong năm tệp:
Vehicle.java: Tạo một lớp trừu tượng làm nền tảng cho tất cả phương tiện. Mỗi phương tiện có trườngbrand(String) và trườngyear(int). Thêm hàm khởi tạo để khởi tạo cả hai trường, các phương thức getter cho từng trường và một phương thức trừu tượngstartEngine()trả về String. Thêm cả phương thức cụ thểgetInfo()trả về:[brand] ([year]). Lớp trừu tượng là lựa chọn phù hợp trong trường hợp này: các phương tiện có chung trạng thái (brand, year) và một số hành vi (getInfo), nhưng mỗi loại khởi động động cơ theo cách khác nhau.Convertible.java: Định nghĩa một giao diện dành cho các phương tiện có thể đóng mở mui. Khả năng này không gắn với việc một phương tiện là gì, mà là điều một số phương tiện có thể làm. Khai báo hai phương thức:openRoof()vàcloseRoof(), cả hai đều trả về String.Car.java: Tạo một lớp kế thừaVehiclevà triển khaiConvertible. Một Car có thêm trườngnumDoors(int). Dùngsuperđể khởi tạo các trường kế thừa. Triển khaistartEngine()để trả về:[brand] car engine started. Triển khaiopenRoof()để trả về:[brand] roof openingvàcloseRoof()để trả về:[brand] roof closing.Motorcycle.java: Tạo một lớp kế thừaVehiclenhưng KHÔNG triển khaiConvertible: xe máy không có mui! Một Motorcycle có trườnghasSidecar(boolean). Triển khaistartEngine()để trả về:[brand] motorcycle engine roaring.Main.java: Kết hợp mọi thứ để minh họa sự khác nhau giữa lớp trừu tượng và giao diện. Bạn sẽ nhận bốn đầu vào: nhãn hiệu ô tô, năm sản xuất của ô tô, nhãn hiệu xe máy và năm sản xuất của xe máy.Tạo một Car (có 4 cửa) và một Motorcycle (không có thùng phụ). Trước tiên, minh họa hành vi chung từ lớp trừu tượng bằng cách in ra
getInfo()cho cả hai phương tiện. Cả hai đều kế thừa phương thức này từ Vehicle. Sau đó, in kết quả củastartEngine()cho từng phương tiện: hãy chú ý cách mỗi loại phương tiện triển khai phương thức này khác nhau.Cuối cùng, minh họa khả năng của giao diện: vì chỉ Car triển khai
Convertible, hãy gọi và inopenRoof(), rồi đếncloseRoof(), chỉ trên ô tô.
Bạn sẽ nhận bốn đầu vào: nhãn hiệu ô tô (String), năm sản xuất của ô tô (int), nhãn hiệu xe máy (String) và năm sản xuất của xe máy (int).
Đầu ra của bạn cần có tổng cộng sáu dòng. Hãy chú ý cách cả hai phương tiện đều dùng chung trạng thái từ lớp trừu tượng và phương thức getInfo(), nhưng chỉ Car có khả năng đóng mở mui. Điều này minh họa khi nào nên dùng mỗi cách!
Tự làm thử
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Đọc dữ liệu đầu vào
String carBrand = scanner.nextLine();
int carYear = scanner.nextInt();
scanner.nextLine(); // Đọc ký tự xuống dòng còn lại
String motorcycleBrand = scanner.nextLine();
int motorcycleYear = scanner.nextInt();
// TODO: Tạo một Car có 4 cửa
// TODO: Tạo một Motorcycle không có thùng bên (false)
// TODO: In ra getInfo() cho cả hai xe (minh họa hành vi chung của lớp trừu tượng)
// TODO: In ra startEngine() cho cả hai xe (minh họa các cách triển khai khác nhau)
// TODO: Chỉ in ra openRoof() và closeRoof() cho ô tô (minh họa khả năng của giao diện)
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online