1.Giới thiệu

Tính trừu tượng (Abstruction)là một trong những tính chất chính của lập trình hướng đối tượng. Nó cho phép chúng ta che giấu sự phức tạp khi triển khai chỉ bằng cách cung cấp các chức năng thông qua các giao diện đơn giản hơn. Trong Java, chúng ta đạt được sự trừu tượng bằng cách sử dụng một giao diện interface hoặc một lớp trừu tượng(abstruct).

Trong bài viết này, chúng ta sẽ thảo luận khi nào sử dụng interface (giao diện) và khi nào sử dụng một lớp trừu tượng trong khi thiết kế ứng dụng. Ngoài ra, sự khác biệt chính giữa chúng và cái nào nên chọn để đạt được hiệu quả mà chúng ta mong muốn.

2. Lớp và giao diện

Đầu tiên, chúng ta hãy xem xét sự khác biệt giữa một lớp bình thường và một giao diện.

Lớp là một kiểu do người dùng định nghĩa, hoạt động như một bản thiết kế để tạo đối tượng. Nó có thể có các thuộc tính và phương thức đại diện cho các trạng thái và hành vi tương ứng của một đối tượng.

Một giao diện cũng là một kiểu do người dùng định nghĩa tương tự về mặt cú pháp với một lớp. Nó có thể có một tập hợp các trường là hằng số và ký hiệu phương thức sẽ bị ghi đè bởi các lớp triển khai giao diện.

Ngoài ra, các tính năng mới của Java 8 hỗ trợ các phương thức tĩnh và mặc định trong giao diện để hỗ trợ khả năng tương thích ngược. Các phương thức trong một giao diện là hoàn toàn trừu tượng nếu chúng không phải là phương thức tĩnh hoặc mặc định và tất cả đều là công khai.

Tuy nhiên, bắt đầu với Java 9, chúng ta cũng có thể thêm các phương thức riêng trong các giao diện.

3.Giao diện và lớp trừu tượng

Một lớp trừu tượng hiểu đơn giản là một lớp được khai báo bằng từ khóa abstract. Nó cũng cho phép chúng ta khai báo các chữ ký của phương thức bằng cách sử dụng từ khóa trừu tượng (abstract method) và buộc các lớp con của nó thực thi tất cả các phương thức đã khai báo. Giả sử nếu một lớp có một phương thức là trừu tượng, thì bản thân lớp đó phải là trừu tượng.

Các lớp trừu tượng không có hạn chế về trường và phạm vi phương thức, trong khi trong một giao diện, tất cả đều được công khai theo mặc định. Chúng ta có thể có các khối khởi tạo instance và static trong một lớp trừu tượng, trong khi chúng ta không bao giờ có thể có chúng trong giao diện. Các lớp trừu tượng cũng có thể có các hàm tạo sẽ được thực thi trong quá trình khởi tạo đối tượng con.

Java 8 đã giới thiệu các giao diện chức năng, một giao diện có hạn chế là không có nhiều hơn một phương thức trừu tượng được khai báo. Bất kỳ giao diện nào có một phương thức trừu tượng khác với các phương thức tĩnh và mặc định đều được coi là một giao diện chức năng. Chúng ta có thể sử dụng tính năng này để hạn chế số lượng các phương thức trừu tượng được khai báo. Trong khi ở trong các lớp trừu tượng, chúng ta không bao giờ có thể có giới hạn này về số lượng khai báo các phương thức trừu tượng.

Các lớp trừu tượng tương tự như các giao diện theo một số cách:

  • Chúng ta không thể khởi tạo một trong hai. tức là chúng ta không thể sử dụng trực tiếp câu lệnh new TypeName () để khởi tạo một đối tượng. Nếu chúng ta đã sử dụng câu lệnh nói trên, chúng ta phải ghi đè tất cả các phương thức bằng cách sử dụng một lớp ẩn danh
  • Cả hai đều có thể chứa một tập hợp các phương thức được khai báo và định nghĩa có hoặc không có sự triển khai của chúng. tức là các phương thức tĩnh & mặc định (được định nghĩa) trong một giao diện, các phương thức toàn cục (được định nghĩa) trong lớp trừu tượng, các phương thức trừu tượng (được khai báo) trong cả hai phương thức đó

4.Sử dụng giao diện khi nào?

Hãy xem xét một số tình huống khi một người nên sử dụng một giao diện:

Nếu vấn đề cần được giải quyết bằng cách sử dụng nhiều kế thừa và bao gồm các phân cấp lớp khác nhau

Khi các lớp không liên quan thực hiện giao diện của chúng tôi. Ví dụ: Comparable cung cấp phương thức CompareTo () có thể được ghi đè để so sánh hai đối tượng

Khi các chức năng ứng dụng phải được định nghĩa như một giao kèo, nhưng không quan tâm đến việc ai thực hiện hành vi. tức là, các nhà cung cấp bên thứ ba cần triển khai đầy đủ

Cân nhắc sử dụng giao diện khi vấn đề của chúng ta đưa ra thể hiện “A có khả năng [làm được điều này]”.

Ví dụ: “Có thể sao chép có khả năng sao chép một đối tượng”, “Có thể vẽ có khả năng vẽ một hình”, v.v.

Chúng ta hãy xem xét một ví dụ sử dụng giao diện:

public interface Sender {
    void send(File fileToBeSent);
}
public class ImageSender implements Sender {
    @Override
    public void send(File fileToBeSent) {
        // image sending implementation code.
    }
}

Ở đây, Sender là một giao diện có phương thức send (). Do đó, Sender có thể gửi một tệp”, chúng ta đã triển khai nó dưới dạng một giao diện. ImageSender thực hiện giao diện để gửi một hình ảnh đến mục tiêu. Chúng ta có thể sử dụng thêm giao diện trên để triển khai VideoSender, DocumentSender để hoàn thành các công việc khác nhau.

Hãy xem xét một trường hợp kiểm thử đơn vị sử dụng giao diện trên và lớp được triển khai của nó:

@Test
void givenImageUploaded_whenButtonClicked_thenSendImage() { 
 
    File imageFile = new File(IMAGE_FILE_PATH);
 
    Sender sender = new ImageSender();
    sender.send(imageFile);
}

5.Sử dụng lớp trừu tượng khi nào?

Bây giờ, chúng ta hãy xem một số tình huống khi nào ta nên sử dụng lớp trừu tượng:

Khi cố gắng sử dụng khái niệm kế thừa trong code (chia sẻ code giữa nhiều lớp có liên quan), bằng cách cung cấp các phương thức chung của lớp cơ sở mà các lớp con ghi đè
Nếu chúng ta có các yêu cầu cụ thể và chỉ một phần chi tiết triển khai

Trong khi các lớp mở rộng của các lớp trừu tượng có một số trường hoặc phương thức phổ biến (yêu cầu các sửa đổi không công khai)

Nếu một người muốn có các phương thức non-final hoặc non-static để sửa đổi các trạng thái của một đối tượng

Cân nhắc sử dụng các lớp trừu tượng và kế thừa khi vấn đề của chúng ta đưa ra minh chứng “A là B”. Ví dụ: “Chó là động vật”, “Lamborghini là ô tô”, v.v.

Hãy xem một ví dụ sử dụng lớp trừu tượng:

public abstract class Vehicle {
    
    protected abstract void start();
    protected abstract void stop();
    protected abstract void drive();
    protected abstract void changeGear();
    protected abstract void reverse();
    
    // standard getters and setters
}
public class Car extends Vehicle {

    @Override
    protected void start() {
        // code implementation details on starting a car.
    }

    @Override
    protected void stop() {
        // code implementation details on stopping a car.
    }

    @Override
    protected void drive() {
        // code implementation details on start driving a car.
    }

    @Override
    protected void changeGear() {
        // code implementation details on changing the car gear.
    }

    @Override
    protected void reverse() {
        // code implementation details on reverse driving a car.
    }
}

Trong đoạn code trên, lớp Vehicle đã được định nghĩa là trừu tượng cùng với các phương thức trừu tượng khác. Nó cung cấp các hoạt động chung của bất kỳ phương tiện nào trên thế giới thực và cũng có một số chức năng chung. Lớp Car , kế thừa lớp Vehicle, ghi đè tất cả các phương thức bằng cách cung cấp các chi tiết triển khai của Car (“Car là một Vehicle”).

Do đó, chúng ta định nghĩa lớp Vehicle là lớp trừu tượng, trong đó các chức năng có thể được thực hiện bởi bất kỳ phương tiện thực tế riêng lẻ nào như ô tô và xe buýt. Ví dụ, trong thế giới thực, khởi động ô tô và xe buýt sẽ không bao giờ giống nhau (mỗi loại cần các chi tiết thực hiện khác nhau).

Bây giờ, hãy xem xét một unit test đơn giản sử dụng code trên:

@Test
void givenVehicle_whenNeedToDrive_thenStart() {

    Vehicle car = new Car("BMW");

    car.start();
    car.drive();
    car.changeGear();
    car.stop();
}

6.Kết luận

Bài viết này đã thảo luận về tổng quan của các giao diện (interface) và các lớp trừu tượng cùng sự khác biệt chính giữa chúng. Ngoài ra, chúng tôi đã gợi ý thời điểm sử dụng chúng trong công việc để giúp cho việc viết code linh hoạt và sạch sẽ.

Mã nguồn hoàn chỉnh cho các ví dụ được đưa ra trong bài viết này có sẵn trên GitHub.


Tham khảo tại: https://www.baeldung.com/java-interface-vs-abstract-class