Menu
CoddyTech

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ăngLớp trừu tượngGiao diện
Biến thực thểCóChỉ hằng số
Hàm khởi tạoCóKhông
Kế thừa nhiều lớpKhông (chỉ extends một lớp)Có (implements nhiều giao diện)
Bộ điều chỉnh quyền truy cậpBấ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.

challenge icon

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ường brand (String) và trường year (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ượng startEngine() 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ừa Vehicle và triển khai Convertible. Một Car có thêm trường numDoors (int). Dùng super để khởi tạo các trường kế thừa. Triển khai startEngine() để trả về: [brand] car engine started. Triển khai openRoof() để trả về: [brand] roof opening và closeRoof() để trả về: [brand] roof closing.
  • Motorcycle.java: Tạo một lớp kế thừa Vehicle nhưng KHÔNG triển khai Convertible: xe máy không có mui! Một Motorcycle có trường hasSidecar (boolean). Triển khai startEngine() để 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ủa startEngine() 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à in openRoof(), rồi đến closeRoof(), 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)
    }
}
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