Để hiểu khái niệm lập trình hướng đối tượng, ta sẽ xoáy vào 3 khái niệm nhỏ sau: đối tượng (object), lớp (class) và mối quan hệ của Lớp với Đối tượng và với bản thân Lớp.
1. Đối tượng (object) là gì?
Đối tượng là các thực thể hoặc phi thực thể (hay còn gọi là phi vật thể) có đặc tính và hành động hoặc khả năng riêng biệt.
Ví dụ:
- David Beckham
- Đặc tính: cao 1.83 m; đẹp trai :D, có râu…
- Khả năng: sút xa, chuyền dài, chạy…
- Lamborghini Aventador 2016
- Đặc tính: 740 mã lực, nặng 1,9 tấn, sang trọng, dòng xe coupe…
- Khả năng: tăng tốc nhanh, chạy nhanh, phát sáng bằng đèn
- TV Samsung Ultra HD
- Đặc tính: dài 110 cm, cao 70 cm, màn hình phẳng…
- Khả năng: phát hình ảnh từ cáp truyền hình, giải mã tín hiệu..
Dễ hiểu mà. Nếu dùng Google search tiếng Việt, bạn sẽ có ngay kết quả đầu tiên cho từ khóa “đối tượng” nằm trên trang Wikipedia.
2. Lớp (class) là gì?
Là một phương pháp theo đó các nhà sinh học gom nhóm và phân loại các loài sinh vật — Wikipedia
Àh ý mình dù là Sinh Học hay Lập Trình thì cũng có cách phân loại giống nhau cả thôi. Vậy ta có thể kết luận rằng: lớp là khuôn mẫu chung, là nhóm chung, cấu trúc chung, là cách phân loại chung… cho các đối tượng.
Trong tiếng Anh, class có thể có 2 nghĩa: “lớp học” (classroom) hay “phân loại” (classification). Giờ thì bạn biết class này là loại nào rồi nhể?
Vậy dùng lại mấy ví dụ ở trên, Lớp sẽ được định nghĩa đại khái như sau:
- David Beckham, Christiano Ronaldo hay Lionel Messi đều thuộc cùng lớp “Cầu thủ bóng đá”
Đặc tính chung: chiều cao; cân nặng, khuôn mặt…
Khả năng chung: sút, chuyền, chạy…
- Lamborghini Aventador 2016, MINI Cooper 2017 đều thuộc cùng lớp “Xe hơi”
Đặc tính chung: sức mạnh, cân nặng…
Khả năng chung: tăng tốc, vận hành, phát sáng…
Các bạn để ý, ví dụ về lớp không hề có tính từ hoặc đặc tả giá trị cụ thể nhé: chạy nhanh, nặng 1,9 tấn, khuôn mặt đẹp trai… Đơn giản, lớp là khuôn mẫu chung, không đại diện cho đối tượng cụ thể nào.
3. Lớp và mối quan hệ với Đối tượng
Nếu như Sinh học và Kỹ thuật giúp ta hiểu Lớp và Đối tượng, thì Triết học sẽ giúp ta hình dung ra mối quan hệ này.
Không, mình sẽ không liệt kê ra hết đâu, chỉ đề cập tới những khái niệm cơ bản và dễ hiểu nhất trong bài này thôi.
A. Tính thừa kế (inheritance):
Wei wei, không phải thừa kế tiền bạc đâu. Mà là thừa kế đặc điểm của bản thân đối tượng. Lại sử dụng ví dụ cũ để giải thích nhé:
- Tất cả các cầu thủ bóng đá đều biết sút, đều phải có chiều cao nhất định…
- Tất cả xe hơi đều phải biết chạy (vận hành), và có tốc độ…
Đấy, đó chính là tính kế thừa.
B. Tính đóng gói (encapsulation):
Vì tính chất này khá trừu tượng, nên thiệt là khó để ra ví dụ cho nó, mặc dù khái niệm thì dễ hiểu thôi.
- Với cầu thủ bóng đá hay xe hơi, tất cả các đặc tính và khả năng đều được “gom” vào cùng 1 đối tượng để dễ quản lý.
C. Tính đa hình (polymorphism):
- David Beckham sút thì trật ra ngoài, Messi sút thì vào gôn. Cơ chế đa hình sẽ giúp chương trình chọn đúng hành động sút ra ngoài hay sút vào gôn cho đúng đối tượng.
- Lamborghini chạy được 300 km/h, nhưng MINI Cooper chạy chỉ được 200 km/h thôi. Cơ chế đa hình sẽ giúp chương trình chọn đúng tốc độ của xe.
Và ngoài ra còn hàng tá các tính chất nhỏ nhỏ con con khác như tính bảo mật, tính trừu tượng… Các bạn học sâu OOP rồi thì sẽ hiểu thôi.
Để minh họa cho sự khác biệt 2 khái niệm “đối tượng” và “luồng logic” trong cách tiếp cận lập trình, mình sẽ lấy kịch bản sau đây của một số nhân vật trong bộ phim ăn khách Transformers ra làm ví dụ:
- 2 nhóm nhân vật: Autobot và Decepticon.
- Kịch bản: 2 nhóm cùng giao chiến để giành tàu vũ trụ
Giả sử ta viết chương trình theo kịch bản đó với phương pháp tiếp cận cũ “luồng logic”, chương trình có thể giống với đoạn mã giả (pseudo-code) sau:
bắt đầu //C++: void main(); Pascal: begin
{
tọa_độ_tàu = lấy_tọa_độ_của_tàu(); // Không lấy thì ma nó mới biết phải chạy đi đâu
cho_megatron_chạy (tọa_độ_tàu); // đây là 1 hàm, tham số là tọa_độ_tàu
tọa_độ_megatron = lấy_tọa_độ_của_megatron();
cho_optimus_bắn(tọa_độ_megatron);
tọa_độ_đạn_optimus = lấy_tọa_độ_đạn_của_optimus() ;
cho_megatron_né (tọa_độ_đạn_optimus) ;
cho_bumblebee_chạy (tọa_độ_tàu); // Hehe, Optimus yểm trợ đồng đội ý mà
tọa_độ_bumblebee = lấy_tọa_độ_của_bumblebee() ;
cho_starscream_bay (tọa_độ_bumblebee);
cho_starscream_tấn_công (tọa_độ_bumblebee); // xxxx
cho_optimus_leo_lên_tàu(tọa_độ_tàu);
...
}
kết thúc
Rồi, vậy giờ ta lại thể hiện tiếp phần kịch bản này nhưng theo hướng đối tượng nhé, các bạn sẽ thấy được sự vượt trội so với cách cũ:
bắt đầu
{
Megatron.chạy(Tàu.tọa_độ);
Optimus.bắn(Megatron.tọa_độ);
Megatron.né(Đạn.tọa_độ);
Bumblebee.chạy(Tàu.tọa_độ);
Starscream.bay(Bumblebee.tọa_độ);
Starscream.bắn(Bumblebee.tọa_độ);
Optimus.điều_khiển(Tàu);
...
}
kết thúc
Thấy sao, rõ là đoạn này dễ đọc, dễ hiểu hơn luồng logic nhiều hẻ? Thay vì, phải lập trình cho từng hàm riêng lẻ, ta có thể tận dụng tất cả các tính chất vượt trội của Lập trình hướng đối tượng:
- Dùng tính thừa kế, viết 1 hàm bắn() cho tất cả đối tượng transformer.
- Dùng tính đa hình, mỗi hàm bắn(), chạy() được thể hiện khác nhau với mỗi đối tượng transformer.
- Dùng tính đóng gói, viết hàm tọa_độ() , bắn() hay chạy() cho mỗi đối tượng transformer.
Với mỗi ngôn ngữ lập trình, mã nguồn sẽ được thể hiện khác nhau (nhưng mình đảm bảo không khác nhau nhiều đâu): C++ khá linh động, có thể vừa viết theo “luồng logic” hay hướng “đối tượng” chung trong một chương trình; hay Java “thuần” hướng đối tượng sẽ buộc các bạn phải viết tất cả trong 1 lớp…