CodeGym /Các khóa học /JAVA 25 SELF /Trừu tượng hóa trong OOP: vì sao cần và cách áp dụng

Trừu tượng hóa trong OOP: vì sao cần và cách áp dụng

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Khái niệm trừu tượng hóa

Nói ngắn gọn: trừu tượng hóa là nghệ thuật nhìn những điều phức tạp một cách đơn giản.

Trong lập trình, trừu tượng hóa là quá trình trích ra các đặc điểm và hành vi chung của một nhóm đối tượng, trong khi bỏ qua những chi tiết riêng lẻ (ví dụ, chính xác thứ gì đó hoạt động “bên dưới nắp capo” ra sao). Hãy tưởng tượng bạn vẽ bản đồ thành phố: trên đó có đường, tòa nhà, sông — nhưng không có chi tiết như màu rèm ở từng cửa sổ. Bản đồ là một sự trừu tượng của thành phố.

Trong OOP, trừu tượng hóa là tạo ra các lớp và interface chỉ phản ánh những thuộc tính và hành động quan trọng cho bài toán, che giấu các chi tiết không cần thiết.

Ví dụ từ đời sống

  • Phương tiện giao thông — là một trừu tượng. Không quan trọng đó là xe buýt, xe đạp hay tàu vũ trụ — điều cốt lõi là phương tiện có điểm chung: có thể di chuyển, có hành khách và tài xế.
  • Động vật — cũng là một trừu tượng. Tất cả động vật có thể thở, ăn, di chuyển — còn cách chúng làm điều đó thì tùy thuộc vào loài cụ thể.
  • Thanh toán — là một trừu tượng trong phần mềm ngân hàng. Bạn không phải lúc nào cũng bận tâm đó là thanh toán bằng thẻ, qua PayPal hay bằng bitcoin — điều quan trọng là có thể thực hiện và nhận kết quả.

Vì sao trừu tượng hóa quan trọng?

  • Ít phải nghĩ về các chi tiết không quan trọng đối với việc giải quyết nhiệm vụ hiện tại.
  • Thiết kế các hệ thống dễ mở rộng và dễ bảo trì hơn.
  • Làm việc với các đối tượng thông qua một giao diện chung, không cần quan tâm đến hiện thực cụ thể.

2. Trừu tượng hóa trong Java

Trong Java, trừu tượng hóa được hiện thực bằng hai công cụ chính:

  1. Lớp trừu tượng (abstract class)
  2. Interface (interface)

Trong bài giảng này, chúng ta sẽ tập trung vào abstract class. (Interface sẽ được đề cập rất sớm!)

Abstract class là lớp không dành cho việc tạo đối tượng trực tiếp. Nó đặt ra một nền tảng (khuôn mẫu) chung cho các lớp khác. Trong một abstract class, bạn có thể định nghĩa cả các phương thức đã được triển khai (có thân) lẫn các phương thức trừu tượng (không có thân) — những phương thức mà bắt buộc phải được hiện thực trong các lớp con.

Phương thức trừu tượng là phương thức không có triển khai, tức là không có thân. Nó chỉ ra rằng: “Mọi lớp con của lớp này phải hiện thực phương thức này theo cách của riêng mình”.

Ví dụ: trừu tượng hóa “Hình”

public abstract class Shape {
    public abstract void draw(); // Phương thức trừu tượng — không có thân!
}

Ở đây chúng ta nói: “Mọi hình đều có thể được vẽ, nhưng tôi không biết chính xác vẽ thế nào — hãy để mỗi lớp con tự quyết định.”

Vì sao không phải lúc nào cũng cần biết chi tiết triển khai?

Khi sử dụng trừu tượng hóa, bạn làm việc với đối tượng thông qua “bộ mặt” của nó — tập các phương thức mà nó bắt buộc phải hỗ trợ. Cách phương thức này hoạt động cụ thể ra sao — không quan trọng.

Ví dụ, bạn gọi payment.process() trên một đối tượng để thực hiện thanh toán. Không quan trọng nó được triển khai như thế nào — miễn là hoạt động. Điều này cho phép:

  • Thay thế một hiện thực bằng hiện thực khác mà không cần viết lại phần mã sử dụng nó.
  • Đơn giản hóa việc kiểm thử (có thể thay thế hiện thực bằng “stub”).
  • Làm cho mã linh hoạt hơn và bền vững hơn trước thay đổi.

3. Lợi ích của trừu tượng hóa

Đơn giản hóa thiết kế và bảo trì mã
Trừu tượng hóa giúp bạn không phải nghĩ về những thứ dư thừa. Bạn không cần biết động cơ ô tô hoạt động ra sao để điều khiển nó — chỉ cần vô lăng, bàn đạp và hướng dẫn “chạy thẳng”. Tương tự trong mã: nếu bạn làm việc với abstract class hoặc interface, bạn chỉ thấy những gì mình cần.

Dễ dàng mở rộng hệ thống
Khi hệ thống của bạn được xây trên các trừu tượng, bạn dễ dàng thêm các loại đối tượng mới. Ví dụ, nếu bạn có abstract class Shape, bạn có thể thêm một loại hình mới — Triangle, mà không phải sửa mã cũ.

Giảm độ kết dính giữa các thành phần
Nếu các phần khác nhau của chương trình chỉ giao tiếp thông qua các trừu tượng, chúng có thể thay đổi độc lập với nhau. Giống như ổ cắm và phích cắm: nếu chuẩn khớp nhau, bạn có thể cắm bất kỳ thiết bị nào vào ổ.

4. Ví dụ thực hành

Hãy xem trừu tượng hóa trông như thế nào trong thực tế. Chúng ta sẽ dùng các ví dụ có thể nhúng vào ứng dụng học tập của bạn để bạn có thể “sờ tận tay”.

Ví dụ 1: Lớp “Shape” (Hình)

public abstract class Shape {
    public abstract void draw();
}

Bây giờ tạo một vài hình cụ thể:

public class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Vẽ hình tròn");
    }
}

public class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("Vẽ hình chữ nhật");
    }
}

Sử dụng trừu tượng hóa:

Shape s1 = new Circle();
Shape s2 = new Rectangle();

s1.draw(); // Vẽ hình tròn
s2.draw(); // Vẽ hình chữ nhật

Ở đây chúng ta làm việc với các biến kiểu Shape — và không quan trọng bên trong là hình nào. Đó chính là sức mạnh của trừu tượng hóa!

Ví dụ 2: Lớp “Payment”

public abstract class Payment {
    public abstract void process();
}

Hiện thực cụ thể:

public class CreditCardPayment extends Payment {
    @Override
    public void process() {
        System.out.println("Xử lý thanh toán bằng thẻ tín dụng");
    }
}

public class PaypalPayment extends Payment {
    @Override
    public void process() {
        System.out.println("Xử lý thanh toán qua PayPal");
    }
}

Cách sử dụng:

Payment[] payments = {
    new CreditCardPayment(),
    new PaypalPayment()
};

for (Payment p : payments) {
    p.process();
}

Kết quả là mỗi thanh toán được xử lý theo cách riêng của nó, nhưng phần mã gọi chúng thì không cần bận tâm.

5. Trừu tượng hóa và chi tiết triển khai: làm sao không rối

Trừu tượng hóa là “cái gì”, không phải “như thế nào”

Khi bạn thiết kế trừu tượng hóa, bạn trả lời câu hỏi: “Đối tượng cần làm gì?”
Còn chi tiết triển khai — đó là “Nó làm điều đó như thế nào?”.

Ví dụ:

  • Trừu tượng hóa: “Bất kỳ hình nào cũng phải có khả năng được vẽ (draw()).”
  • Chi tiết triển khai: “Hình tròn được vẽ bằng đường tròn, hình chữ nhật — bằng bốn đoạn thẳng.”

So sánh từ đời sống

Hãy nhớ chiếc điều khiển TV. Bạn không bận tâm nó truyền tín hiệu như thế nào — điều quan trọng là có nút “bật”, “chuyển kênh” và “tăng âm lượng”. Đó là trừu tượng — tập nút bạn có thể bấm. Còn các kỹ sư thiết kế điều khiển thì phải nghĩ về chi tiết triển khai.

6. Cách xác định trừu tượng hóa trong chương trình

Bước 1. Tìm điểm chung

Hãy xem các đối tượng trong miền bài toán của bạn. Chúng có gì chung? Ví dụ, mọi phương tiện đều có thể chạy, mọi động vật đều có thể ăn, mọi thanh toán đều có thể được thực hiện.

Bước 2. Xác định abstract class

Tạo một abstract class chỉ chứa những gì chung cho tất cả đối tượng.

public abstract class Transport {
    public abstract void move();
}

Bước 3. Hiện thực chi tiết trong các lớp con

public class Car extends Transport {
    @Override
    public void move() {
        System.out.println("Ô tô chạy trên đường");
    }
}

public class Bicycle extends Transport {
    @Override
    public void move() {
        System.out.println("Xe đạp di chuyển bằng cách đạp bàn đạp");
    }
}

Bước 4. Sử dụng trừu tượng hóa trong mã

Transport[] transports = {
    new Car(),
    new Bicycle()
};

for (Transport t : transports) {
    t.move();
}

Mã của bạn làm việc với các trừu tượng — và không phụ thuộc vào chi tiết triển khai.

7. Cân bằng giữa trừu tượng hóa và chi tiết hóa

Lỗi điển hình của người mới — cố gắng biến trừu tượng hóa thành quá chi tiết hoặc, ngược lại, quá chung chung.

  • Nếu trừu tượng hóa quá chung chung (ví dụ, lớp “Object” với phương thức “doSomething”), nó sẽ không mang lại lợi ích gì.
  • Nếu trừu tượng hóa quá chi tiết (ví dụ, “Hình tròn với viền đỏ và bán kính 5”), nó mất ý nghĩa — tốt hơn là viết ngay lớp cụ thể.

Quy tắc vàng: trừu tượng hóa chỉ nên phản ánh những gì thực sự chung và quan trọng cho bài toán của bạn.

8. Các lỗi thường gặp khi làm việc với trừu tượng hóa

Lỗi số 1: cố gắng tạo đối tượng từ abstract class.
Các abstract class không dành cho việc tạo đối tượng trực tiếp. Nếu bạn thử viết Shape s = new Shape();, trình biên dịch sẽ báo lỗi: Cannot instantiate the type Shape. Điều này giống như cố mua thứ gọi là “phương tiện” trong cửa hàng. Không được: bạn phải chọn một chiếc xe đạp, ô tô hoặc xe buýt cụ thể.

Lỗi số 2: quên hiện thực phương thức trừu tượng trong lớp con.
Nếu một lớp kế thừa từ abstract class nhưng không hiện thực tất cả phương thức trừu tượng của nó, lớp đó cũng trở thành abstract — và không thể tạo đối tượng. Hãy kiểm tra rằng bạn đã hiện thực tất cả các phương thức bắt buộc.

Lỗi số 3: trộn lẫn trừu tượng hóa và chi tiết triển khai.
Nếu bạn bắt đầu thêm vào abstract class các chi tiết chỉ cần cho một lớp con duy nhất — đó là dấu hiệu của một trừu tượng kém. Ví dụ, nếu trong abstract class Payment xuất hiện trường cardNumber, trong khi không phải mọi thanh toán đều qua thẻ.

Lỗi số 4: lạm dụng trừu tượng hóa.
Không nên tạo trừu tượng chỉ vì muốn có trừu tượng. Nếu trong hệ thống của bạn chỉ có một loại đối tượng và sẽ không bao giờ mở rộng, trừu tượng hóa chỉ làm mã phức tạp hơn.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Thiết kế thế giới động vật 🦁
Thiết kế thế giới động vật 🦁
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Thêm chó vào vườn thú 🐶
Thêm chó vào vườn thú 🐶
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Hồi sinh phương tiện giao thông trong thành phố 🚗🚲
Hồi sinh phương tiện giao thông trong thành phố 🚗🚲
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Phát triển module thanh toán trực tuyến 💳
Phát triển module thanh toán trực tuyến 💳
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION