Vì sao không hỗ trợ kế thừa nhiều lớp
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 26 trên 87.
Java cố ý ngăn một lớp (class) kế thừa nhiều hơn một lớp. Hạn chế này tồn tại do một vấn đề được gọi là vấn đề kim cương.
Hãy tưởng tượng Java cho phép kế thừa đa lớp. Hãy xem xét tình huống giả định sau:
// Đoạn code này KHÔNG hợp lệ trong Java
public class A {
public void greet() {
System.out.println("Hello from A");
}
}
public class B extends A {
@Override
public void greet() {
System.out.println("Hello from B");
}
}
public class C extends A {
@Override
public void greet() {
System.out.println("Hello from C");
}
}
// KHÔNG được phép trong Java!
public class D extends B, C { }Nếu bạn gọi greet() trên một đối tượng D, phiên bản nào sẽ chạy? Phiên bản từ B hay C? Sự mơ hồ này được gọi là vấn đề kim cương, tên gọi bắt nguồn từ hình dạng kim cương mà sơ đồ kế thừa tạo thành.
Giải pháp của Java rất đơn giản: chỉ cho phép kế thừa đơn từ các lớp. Tuy nhiên, Java có một lựa chọn khác. Một lớp có thể triển khai nhiều giao diện, bạn sẽ tìm hiểu về điều này trong một chương sắp tới.
Các giao diện giải quyết vấn đề kim cương theo cách khác và mang lại cho bạn sự linh hoạt mà không gây ra sự mơ hồ.
// Đoạn code này HOÀN TOÀN hợp lệ trong Java
public class Dog extends Animal implements Runnable, Comparable<Dog> {
// Có thể triển khai nhiều giao diện (interface)
}Lựa chọn thiết kế này giúp mô hình kế thừa (inheritance) của Java luôn rõ ràng và dễ dự đoán, đồng thời vẫn cho phép tái sử dụng code mạnh mẽ thông qua các interface.
Thử thách
DễHãy xây dựng một hệ thống trình phát nhạc để minh họa quy tắc kế thừa đơn (single inheritance) của Java và cho thấy bạn vẫn có thể thiết kế linh hoạt trong giới hạn này.
Hãy tưởng tượng bạn muốn tạo một loa thông minh kết hợp các tính năng của trình phát nhạc và trợ lý giọng nói. Vì Java không cho phép kế thừa từ nhiều lớp, bạn cần thiết kế hệ thống phân cấp lớp cẩn thận bằng cách sử dụng kế thừa đơn.
Bạn sẽ tạo bốn tệp để sắp xếp code:
AudioDevice.java: Tạo lớp cơ sở dùng chung cho mọi thiết bị phát âm thanh. Lớp này cần có:- Một trường riêng tư cho
deviceName(String) - Một hàm khởi tạo nhận tên thiết bị
- Một phương thức
getDeviceName()trả về tên - Một phương thức
playSound()in ra:[deviceName]: Playing audio
- Một trường riêng tư cho
MusicPlayer.java: Tạo một lớp kế thừa từ AudioDevice và bổ sung các tính năng dành riêng cho nhạc:- Một trường riêng tư cho
currentSong(String) - Một hàm khởi tạo nhận tên thiết bị và dùng
super(deviceName) - Một phương thức
setSong(String song)đặt bài hát hiện tại - Ghi đè
playSound()để in ra:[deviceName]: Now playing - [currentSong]
- Một trường riêng tư cho
SmartSpeaker.java: Tạo một lớp kế thừa từ MusicPlayer, không phải cả MusicPlayer lẫn một lớp VoiceAssistant nào đó, vì như vậy sẽ là kế thừa đa! Loa thông minh bổ sung tính năng giọng nói dựa trên trình phát nhạc:- Một trường riêng tư cho
assistantName(String) - Một hàm khởi tạo nhận tên thiết bị và tên trợ lý, dùng
super(deviceName) - Một phương thức
voiceCommand(String command)in ra:[assistantName]: Processing "[command]" - Một phương thức
getFullInfo()in ra:[deviceName] with [assistantName] assistant
- Một trường riêng tư cho
Main.java: Minh họa chuỗi kế thừa nhiều cấp của bạn. Bạn sẽ nhận ba đầu vào: tên thiết bị (String), tên trợ lý (String) và tên bài hát (String). Tạo một SmartSpeaker, đặt bài hát, rồi gọi lần lượt các phương thức sau:getFullInfo()playSound()voiceCommand("skip to next")
Bạn sẽ nhận ba đầu vào: tên thiết bị, tên trợ lý và tên bài hát.
Hãy chú ý cách SmartSpeaker có thêm khả năng phát nhạc bằng cách kế thừa theo chuỗi từ MusicPlayer (AudioDevice -> MusicPlayer -> SmartSpeaker), thay vì cố kế thừa từ nhiều lớp không liên quan. Đây là cách Java giúp kế thừa rõ ràng và không mơ hồ!
Tự làm thử
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Đọc các giá trị đầu vào
String deviceName = scanner.nextLine();
String assistantName = scanner.nextLine();
String songName = scanner.nextLine();
// TODO: Tạo một SmartSpeaker với deviceName và assistantName
// TODO: Đặt bài hát bằng setSong()
// TODO: Gọi getFullInfo()
// TODO: Gọi playSound()
// TODO: Gọi voiceCommand("skip to next")
}
}
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