Menu
CoddyTech

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.

challenge icon

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
  • 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]
  • 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
  • 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")
    }
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch Java online