Nội dung chính
Tính trừu tượng trong C++
Trừu tượng hóa dữ liệu (Data abstraction) liên quan tới việc chỉ cung cấp thông tin cần thiết tới bên ngoài và ẩn chi tiết cơ sở của chúng, ví dụ: để biểu diễn thông tin cần thiết trong chương trình mà không hiển thị chi tiết về chúng.
Trừu tượng hóa dữ liệu (Data abstraction) là một kỹ thuật lập trình mà dựa trên sự phân biệt của Interface và Implementation (trình triển khai).
Xem xét ví dụ về một chiếc TV, bạn có thể bật/tắt, thay đổi kênh, chỉnh âm lượng, và thêm các thiết bị ngoại vi như loa, VCR và DVD. Nhưng bạn không biết các chi tiết nội vi của nó, đó là, bạn không biết cách nó nhận tín hiệu qua không khí hoặc qua dây cáp, cách phiên dịch chúng và cuối cùng là hiển thị chúng trên màn hình.
Vì thế, có thể nói rằng một chiếc TV phân biệt rõ ràng trình triển khai nội vi của nó với giao diện ngoại vi và bạn có thể thao tác với interface với các nút nguồn, điều khiển âm lượng mà không cần có bất kỳ hiểu biết về những gì diễn ra bên trong nó.
Bây giờ, về mặt ngôn ngữ lập trình C++, thì các lớp C++ cung cấp Trừu tượng hóa dữ liệu (Data abstraction) ở mức thật tuyệt vời. Chúng cung cấp đủ các phương thức public tới bên ngoài để thao tác với tính năng của đối tượng và để thao tác dữ liệu đối tượng, ví dụ: trạng thái mà không cần thực sự biết về cách lớp đó đã được triển khai nội tại.
Ví dụ, chương trình của bạn có thể tạo một lời gọi tới hàm sort() mà không cần biết về hàm đó thực sự sử dụng thuật toán gì để sắp xếp các giá trị đã cho. Thực ra, trình triển khai cơ sở (underlying implementation) của tính năng sắp xếp có thể thay đổi tùy vào thư viện, và miễn là Interface vẫn như cũ thì lời gọi hàm của bạn vẫn tiếp tục làm việc.
Trong C++, chúng ta sử dụng các Lớp để định nghĩa kiểu dữ liệu trừu tượng (abstract data types (ADT)) của riêng chúng ta. Bạn có thể sử dụng đối tượng cout của lớp ostream cho luồng dữ liệu tới đầu ra chuẩn như sau:
#include <iostream> using namespace std; int main( ) { cout << "Hello C++" <<endl; return 0; }
Tại đây, bạn không cần hiểu cách cout hiển thị văn bản trên màn hình. Bạn chỉ cần biết Public Interface và Underlying Implementation của cout là sẵn sàng để thay đổi.
Nhãn truy cập (Access Label) trong C++
Trong C++, chúng ta sử dụng Access Label để định nghĩa Abstract Interface (Giao diện trừu tượng) cho lớp. Một lớp có thể chứa 0 hoặc nhiều Access Label.
Các thành viên được định nghĩa với một nhãn public là có thể truy cập cho tất cả các phần của chương trình. Trừu tượng hóa dữ liệu của một kiểu được định nghĩa bởi các thành viên public của nó.
Các thành viên được định nghĩa với một nhãn private là không thể truy cập cho code mà sử dụng lớp đó. Khu vực private ẩn trình triển khai này với code mà sử dụng kiểu đó.
Không có hạn chế về tần suất mà một nhãn truy cập (Access Label) có thể xuất hiện. Mỗi Access Label xác định độ truy cập (Access Level) của sự định nghĩa thành viên tiếp theo. Độ truy cập đã xác định còn tồn tại hiệu quả tới khi gặp Access Label kế tiếp hoặc gặp dấu ngoặc móc đóng của thân lớp.
Lợi ích của Trừu tượng hóa dữ liệu trong C++
Trừu tượng hóa dữ liệu trong C++ mang lại hai lợi thế quan trọng:
Phần nội vi hay bên trong lớp được bảo vệ tránh khỏi các lỗi do người dùng vô ý, mà có thể gây hư hỏng trạng thái của dữ liệu.
Triển khai lớp có thể tiến hành qua thời gian để đáp ứng yêu cầu thay đổi hoặc bug các báo cáo mà không yêu cầu thay đổi trong code của người dùng.
Bằng việc định nghĩa các thành viên dữ liệu chỉ trong khu vực private của lớp, tác giả của lớp có thể tự do tạo các thay đổi trong dữ liệu. Nếu trình triển khai thay đổi, thì chỉ mã hóa lớp là cần kiểm tra để biết khía cạnh nào đem lại thay đổi. Nếu dữ liệu là public, thì khi đó bất kỳ hàm nào mà truy cập một cách trực tiếp tới các thành viên dữ liệu của phép biểu diễn cũ có thể bị phá vỡ.
Ví dụ về Trừu tượng hóa dữ liệu trong C++
Trong bất kỳ chương trình C++ nào, nơi bạn triển khai một lớp với các thành viên là public và private, thì đó là một ví dụ của trừu tượng hóa dữ liệu. Bạn xem xét ví dụ sau:
#include <iostream> using namespace std;class A { public: // khai bao constructor A(int i = 0) { tong = i; } // du lieu ma la nhin thay voi ben ngoai void congThem(int motso) { tong += motso; } // du lieu ma la nhin thay voi ben ngoai int tinhTong() { return tong; }; private: // du lieu ma la bi an voi ben ngoai int tong; }; int main() { A a; a.congThem(10); a.congThem(20); a.congThem(50); cout << "Tong gia tri la: " << a.tinhTong() << endl; return 0; }
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Lớp trên cộng hai số và trả về tổng của chúng. Các thành viên public là congThem và tinhTong là các Interface (mà là nhìn thấy) tới bên ngoài và một người sử dụng cần biết chúng để sử dụng lớp đó. Thành viên private là tong là cái gì đó mà người sử dụng không cần biết đến, nhưng là cần thiết cho lớp đó hoạt động một cách chính xác.
Chiến lược thiết kế trong C++
Trừu tượng hóa dữ liệu phân biệt code thành Interface và Implementation. Vì thế, trong khi thiết kế thành phần của bạn, bạn phải giữ Interface độc lập với Implementation, để mà nếu bạn thay đổi underlying implementation thì Interface sẽ vẫn còn tồn tại như cũ.
Trong trường hợp này, bất kỳ chương trình nào đang sử dụng các Interface này, chúng sẽ không bị ảnh hưởng và sẽ cần một sự tái biên dịch với Implementation mới nhất này.