Tính trừu tượng trong OOP: Che giấu sự phức tạp
Bạn lái xe bằng cách nhấn bàn đạp và xoay vô lăng. Bạn không cần phải hiểu về việc phun nhiên liệu hay chuyển động của piston. Chiếc xe che giấu những chi tiết này khỏi bạn. Bạn chỉ sử dụng một giao diện đơn giản.
Phần mềm cũng hoạt động theo cách tương tự.
Hãy nghĩ về việc gửi một email. Mã nguồn của bạn có thể trông như thế này:
emailService.send(email);
Trông nó có vẻ đơn giản. Nhưng đằng sau hậu trường, hệ thống thực hiện rất nhiều tác vụ:
- Xác thực địa chỉ.
- Kết nối với máy chủ SMTP.
- Xác thực người dùng.
- Xây dựng nội dung tin nhắn.
- Xử lý việc thử lại (retries).
- Ghi nhật ký lỗi (logs errors).
Bạn không cần phải thấy các bước này. Bạn chỉ quan tâm rằng email được gửi đi.
Nếu bạn phơi bày mọi bước, bạn sẽ tạo ra vấn đề. Hãy tưởng tượng nếu mọi lập trình viên đều phải gọi thủ công:
connect()authenticate()buildMessage()sendMessage()disconnect()
Điều này khiến mã nguồn của bạn trở nên mong manh. Nếu bạn thay đổi nhà cung cấp email, bạn phải cập nhật mọi nơi trong ứng dụng của mình. Mã nguồn của bạn trở nên bị phụ thuộc chặt chẽ (tightly coupled) vào phần triển khai (implementation).
Tính trừu tượng giải quyết vấn đề này.
Tính trừu tượng phơi bày những gì một đối tượng thực hiện. Nó che giấu cách thức thực hiện. Bạn chỉ cho người dùng thấy những gì họ cần.
Người dùng tương tác với một giao diện ổn định. Họ không quan tâm đến logic xác thực hay chiến lược thử lại của bạn. Những chi tiết đó vẫn được giữ kín.
Nhiều người nhầm lẫn giữa Tính trừu tượng (Abstraction) và Tính đóng gói (Encapsulation). Đây là sự khác biệt:
Tính đóng gói đặt câu hỏi: Ai có thể thay đổi trạng thái của đối tượng này? Nó bảo vệ dữ liệu.
Tính trừu tượng đặt câu hỏi: Những chi tiết nào người dùng nên thấy? Nó che giấu sự phức tạp.
Chúng hoạt động cùng nhau để xây dựng các hệ thống tốt hơn.
Tiếp theo, chúng ta sẽ thảo luận về Tính kế thừa (Inheritance). Chúng ta sẽ tìm hiểu cách xử lý các hành vi dùng chung mà không cần phải viết cùng một đoạn mã hai lần.