1. Đối tượng (Objects)
Khi thiết kế một chương trình theo tư duy hướng đối tượng người ta sẽ không hỏi “vấn đề này sẽ được chia thành những hàm nào” mà là “vấn đề này có thể giải quyết bằng cách chia thành những đối tượng nào”. Tư duy theo hướng đối tượng làm cho việc thiết kế được “tự nhiên” hơn và trực quan hơn.
Điều này xuất phát từ việc các lập trình viên cố gắng tạo ra một phong cách lập trình càng giống đời thực càng tốt. Nếu ngoài đời có cái công nông thì khi thiết kế ta cũng bê nguyên cả cái công nông vào trong chương trình, và như vậy chương trình là tập hợp tất cả các đối tượng có liên quan với nhau. Tất cả mọi thứ đều có thể trở thành đối tượng trong OOP, nếu có giới hạn thì đó chính là trí tưởng của bạn. Đối tượng là một thực thể tồn tại trong khi chương trình chạy. Nó có các thuộc tính (attributes) và phương trức (methods) của riêng mình.
2. Lớp (Classes)
Trong khi đối tượng là một thực thể xác định thì lớp lại là một khái nhiệm trừu tượng. Có thể so sánh lớp như “kiểu dữ liệu còn” đối tượng là “biến” có kiểu của lớp. Ví dụ: lớp Công_nông có thể được mô tả như sau:
Lớp Công_nông
Thuộc tính:
- Nhãn hiệu (ví dụ Lamborghini)
- Màu xe
- Giá xe
- Vận tốc tối đa (ví dụ 300 km/h)
- Khởi động
- Chạy thẳng
- Rẽ trái / phải
- Dừng
- Tắt máy
Công_nông công_nông_của_tôi;
Hoàn toàn tương tự như khai báo:
int my_integer;
Tạo một lớp mới tương tự như tạo ra một kiểu dữ liệu mới – kiểu người dùng tự định nghĩa (user-defined type)
Lớp là “khuôn” để đúc ra các đối tượng.Một đối tượng thuộc lớp Công_nông sẽ có đầy đủ những thuộc tính và phương thức như được mô tả ở trên, trong trường hợp này công_nông_của_tôi được đúc ra từ “khuôn” Công_nông. Có một sự tương ứng giữa lớp và đối tượng nhưng bản chất thì lại khác nhau. Lớp là sự trừu tượng hóa của đối tượng, còn đối tượng là một sự thể hiện (instance) của lớp. Đối tượng là một thực thể có thực, tồn tại trong hệ thống, còn lớp là khái niệm trừu tượng chỉ tồn tại ở dạng khái niệm để mô tả đặc tính chung cho đối tượng. Tất cả những đối tượng của một lớp sẽ có thuộc tính và phương thức giống nhau.
3. Sự đóng gói và trừu tượng hóa dữ liệu (Encapsulation & Data Abstraction)
Nhìn lại thí dụ trên thì mỗi đối tượng thuộc lớp Công_nông sẽ có cả các thuộc tính và phương thức được “đóng gói” chung lại. Muốn truy cập vào các thành phần dữ liệu bắt buộc phải thông qua phương thức, và các phương thức này tạo ra một giao diện để đối tượng giao tiếp với bên ngoài.
Giao diện này giúp cho dữ liệu được bảo vệ và ngăn chặn những truy cập bất hợp pháp, đồng thời tạo ra sự thân thiện cho người dùng. Ví dụ: nếu như trong C, một xâu được lưu trữ trong một mảng str nào đó, muốn biết độ dài của xâu ta phải gọi hàm strlen() trong thư viện <string.h> thì trong C++, nếu str là một đối tượng thuộc lớp string thì tự nó “biết” kích thước của mình, và chỉ cần gọi str.size() hoặc str.length() là nó sẽ trả về độ dài của xâu str.
Người dùng hoàn toàn không cần biết cài đặt chi tiết bên trong lớp string như thế nào mà chỉ cần biết “giao diện” để có thể giao tiếp với một đối tượng thuộc lớp string là ok. Điều này dẫn đến sự trừu tượng hóa dữ liệu. Nghĩa là bỏ qua mọi cài đặt chi tiết và chỉ quan tâm vào đặc tả dữ liệu và các phương thức thao tác trên dữ liệu. Đặc tả về lớp Công_nông ở trên cũng là một sự trừu tượng hóa dữ liệu.
4. Sự kế thừa (Inheritance)
Những ý tưởng về lớp dẫn đến những ý tưởng về kế thừa. Trong cuộc sống hàng ngày chúng ta thấy rất nhiều ví dụ về sự kế thừa (tất nhiên là không phải thừa kế vê tài sản ). Ví dụ:lớp động vật có thể phân chia thành nhiều lớp nhỏ hơn như lớp côn trùng, lớp chim, lớp động vật có vú, không có vú … blah blah … hay lớp phương tiện có thể chia thành các lớp nhỏ hơn như xe đạp, xe thồ, xe tăng, xích lô, … Các lớp nhỏ hơn được gọi là lớp con (subclass) hay lớp dẫn xuất (derived class) còn các lớp phía trên gọi là lớp cha (super class) hay lớp cơ sở (base class).
Một nguyên tắc chung là các lớp con sẽ có các đặc điểm chung được thừa hưởng từ các lớp cha mà nó kế thừa. Ví dụ lớp côn trùng và động vật có vú đều sẽ có những đặc điểm chung của lớp động vật. Và do đó ta chỉ cần bổ sung những đặc điểu cần thiết thay vì viết lại tòan bộ code. Điều này giảm gánh nặng cho các lập trình viên và do đó góp phần giảm chi phí sản xuất cũng như bảo trì, nâng cấp phần mềm.
5. Tính đa hình và sự quá tải (Polymorphism & Overloading)
Giả sử ta xây dựng một lớp String để “đúc” ra các đối tượng lưu trữ xâu ký tự, ví dụ ta có 3 đối tượng s1, s2, s3 thuộc lớp String. Ta muốn thiết kế lớp String sao cho câu lệnh
s3 = s1 + s2 ;
sẽ thực hiện việc nối xâu s2 vào đuôi xâu s1 rồi gán kết quả cho xâu s3. Nếu như vậy công việc lập trình trông sẽ “tự nhiên” hơn. Nhưng thật không may ngôn ngữ lập trình không cung cấp sẵn điều này. Sử dụng các toán tử (operators) + và = như trên sẽ gây lỗi.
Tuy nhiên C++ cung cấp một cơ chế cho phép lập trình viên “định nghĩa lại” các toán tử này để dùng trong các mục đích khác nhau. Việc định nghĩa lại cách sử dụng toán tử được gọi là “quá tải toán tử” (operator overloading). Một số người gọi nó là “nạp chồng toán tử” nhưng mình thích dùng từ quá tải hơn vì nghe nó có vẻ “cơ khí” . C++ cho phép quá tải hầu hết các toán tử thông dụng như +, -, *, /, [], <<, >>, … Ngoài việc cho phép quá tải toán tử, C++ còn cho phép “quá tải hàm” (function overloading), cái này mình sẽ nói kỹ hơn ở bài khác.
Nói chung overloading là một cách cho phép ta sử dụng một toán tử hoặc hàm bằng những cách khác nhau tùy theo ngữ cảnh, và đó một trường hợp của “tính đa hình” (polymorphism), một tính năng rất quan trọng của OOP.