Các khái niệm căn bản trong lập trình hướng đối tượng
Object Oriented Programming a.k.a OOP là bài học vỡ lòng của mọi lập trình viên. Thế nhưng để nhớ rõ về OOP thì không chắc ai cũng có thể. Thực tế có nhiều anh em dev lâu năm, múa code thành thơ, lâu lâu vẫn ôn lại cú nữa cho chắc. Trong series này, mình sẽ tổng hợp các khái niệm lập trình hướng đối tượng căn bản. Biết đâu sau này mình cũng quên thì sao. 😂
Disclaimer: Series này tổng hợp lý thuyết một cách khách quan nhất để anh em ôn lướt sóng cho nhanh. Mình không focus vào ngôn ngữ lập trình hay cú pháp. Sẽ tốt hơn nếu anh em đã có nền sẵn.
MỤC LỤC
Object-Oriented Programming – What & Why
OOP là viết tắt cho Object-Oriented Programming trong tiếng Việt là Lập trình hướng Đối tượng.
Quay ngược quá khứ về thời đồ đá của khoa học máy tính. Thời đại mà các cỗ máy được lập trình bằng cách đấu dây và điều chỉnh công tắc. Đây quả thật là một việc làm tiêu tốn thời gian và công sức. Chính vì sự bất tiện đó nên người ta đã nghĩ ra cách lưu lại chương trình vào những tấm bìa đục lỗ để tiết kiệm công sức. Các câu lệnh lần lượt được thực thi theo thứ tự trên bảng đục lỗ. Thế nhưng yêu cầu các bài toán đưa ra ngày càng phức tạp theo thời gian. Như một lẽ tự nhiên, các ngôn ngữ lập trình cao cấp hơn lần lượt ra đời song song với sự cải tiến phần cứng. Từ hợp ngữ Assembly cho đến các ngôn ngữ bậc cao như Pascal, C, Java hay Python và JavaScript.
Tư duy lập trình hướng thủ tục
Khi làm việc với C hoặc Pascal, chúng ta quan tâm đến các hàm và cách hoạt động của chúng. Đó là đặc điểm của ngôn ngữ hướng thủ tục (Procedural Oriented Programming). Tư tưởng lập trình này mặc dù có rất nhiều ưu điểm. Nhưng lập trình viên vẫn phải tư duy theo cách một cỗ máy hoạt động và bộc lộ nhiều nhược điểm. Đó là lý do một thế hệ ngôn ngữ lập trình mang tư duy hướng đối tượng ra đời.
Thế nào là lập trình hướng đối tượng
Đối với lập trình hướng đối tượng. Chúng ta quan tâm nhiều hơn đến dữ liệu và quan hệ giữa chúng trong thực tế. Hãy xem dữ liệu là đối tượng và đối tượng phải được thiết kế gần gũi với thực tế nhất. Các bài toán được giải quyết bằng cách xây dựng quan hệ giữa các đối tượng và cách chúng thực thi với nhau.
Mục tiêu của lập trình hướng đối tượng:
- Chương trình có cấu trúc, gọn gàng, quy củ hơn.
- Làm cho dữ liệu trực quan, gần gũi với thực tế nhất.
- Gom dữ liệu và chức năng liên quan lại 1 cục.
- “Don’t Repeat Yourself”: Tránh việc viết lại code cũ. Thay vào đó là kế thừa và tái sử dụng.
Quan hệ giữa các đối tượng:
- Kế thừa: Đối tượng con được thừa hưởng các thuộc tính và phương thức từ đối tượng cha.
- Phụ thuộc: Đối tượng
Student
được khai báo là thuộc tính của đối tượngClassroom
thì ta gọi làClassroom
phụ thuộcStudent
. - Song song: Chúng ta méo liên quan gì nhau. Những con đường song song – Chillies
Rõ ràng lập trình hướng thủ tục đem đến sự đơn giản và tiện lợi hơn cho anh em mới nhập môn. Nhưng để phù hợp với thực tế và những yêu cầu phức tạp. Chúng ta cần hiểu và áp dụng các nguyên tắc của lập trình hướng đối tượng.
OOPs Concepts – Các khái niệm cơ bản
Mọi thứ trong lập trình hướng đối tượng xoay quanh hai khái niệm: Class
và Object
.
Ảnh dưới đây sẽ tổng hợp nhưng gì chúng ta sẽ tuốt lại trong ba phần của series Lập trình hướng đối tượng. High quality image
Class và Object là cái chi rứa
Class
là nơi mô tả các thành phần thuộc về một loại đối tượng. Class định nghĩa các thuộc tính và các phương thức. Đôi khi còn chứa class và một vài thứ linh tinh khác.Object
là một cái gì đó được tạo ra từ Class. Có các thuộc tính và có thể thực hiện các hành vi do class định nghĩa.
Như vậy Class giống như một ý tưởng, còn Object là hiện thực của ý tưởng đó. Đôi khi chúng ta sẽ gặp từ instance thay vì object. Cái này liên quan đến ngữ pháp hơn là kỹ thuật nên anh em xem là như nhau đi. 😂 “Object is an instance of a Class”.
Ví dụ thực tế về Class và Object
Bạn là kỹ sư thiết kế xe cho Vinfast. Đầu tiên, bạn thiết kế ra một mẫu mới và đặt tên cho nó là Vinfast Lux. Tốc độ tối đa có thể chạy là 69km/h. Sau đó bạn gửi bản vẽ và thông số đi kiểm định. Cuối cùng nhà máy có thể lắp ráp các linh kiện thành một chiếc xe hoàn chỉnh.
Ở đây, bản thiết kế chính là đại diện cho khái niệm Class. Nó định nghĩa các thuộc tính mà một chiếc xe cần có (tên xe, tốc độ, v.v…) và hành động nó có thể thực hiện (chạy, rẽ, bay). Còn chiếc xe đã được sản xuất đại diện cho Object. Đó là chiếc xe bay được.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Bản thiết kế của xe class Car { string name = "Vinfast Lux"; float speed = 69; // Định nghĩa xe chạy như thế nào public void Run() { Console.WriteLine($"{name} runs at {speed}km/h"); } } // Chế tạo và sử dụng chiếc xe static void Main(string[] args) { Car myCar = new Car(); // Nhà máy tạo ra chiếc xe myCar.Run(); // Xe này chạy được // Car.Run(); // Xe nào chạy? Ai biết đâu? } |
Class Members – Các thành phần của Class
Trong lập trình hướng đối tượng. Một Class chuẩn chỉ sẽ bao gồm 4 thành phần dưới đây. Trong thực tế, một Class không nhất thiết phải đầy đủ các thành phần này. Tuỳ vào mục đích bài toán mà có thể lược bỏ, thêm, bớt.
- Attributes: Thuộc tính lưu các giá trị của Object. Thường là private. (Đối với C# gọi là
Field
). - Getter & Setter: Là các phương thức để thao tác với dữ liệu của
Attributes
. Đảm bảo tính toàn vẹn dữ liệu và tính bao đóng của hướng đối tượng. - Constructor: Gọi là hàm dựng. Phương thức này được gọi đầu tiên khi sử dụng từ khoá
new
để tạo Object mới. Thường được sử dụng để khởi tạo giá trị ban đầu cho các Attributes. - Method: Gọi là phương thức. Đây là các chương trình con quy định cách Object thao tác với dữ liệu như thế nào. Có thể hiểu đây chính là hành động của một đối tượng.
Access Modifiers – Phạm vi truy cập
Access Modifiers
hoặc Access Specifiers
là thuộc tính quy định khả năng truy cập từ bên ngoài dành cho Class và Class Members. Đây là yếu tố quan trọng để đảm bảo tính bao đóng của hướng đối tượng, nghĩa là đảm bảo ẩn đi dữ liệu nhạy cảm khỏi người dùng.
Hầu hết các ngôn ngữ lập trình hỗ trợ 3 access modifier sau:
- public: Thành phần có thể được truy cập ở mọi nơi trong chương trình.
- private: Thành phần chỉ có thể được truy cập trong nội bộ Class.
- protected: Thành phần có thể được truy cập trong nội bộ Class hoặc Class kế thừa nó.
Đối với C# còn có ba kiểu khác:
- internal: Thành phần có thể được truy cập tự do trong nội bộ
assembly
. (cùng project, .dll, .exe) - protected internal: Một sự kết hợp của
protected
vàinternal
. Thành phần có thể được truy cập tự do trong nội bộ assembly hoặc từ Class kế thừa nó (khác assembly nhưng kế thừa là được) hoặc trong nội bộ Class. - private protected: Giống protected nhưng phải cùng assembly.
Đối với Java, chúng ta có thêm kiểu default
hoạt động tương tự như internal
của C#.
Ví dụ về Access modifiers mình gom luôn vào phần bên dưới.
Constructor & Destructor
Trong C#, khái niệm constructor
được gọi khi tạo đối tượng, ngược lại destructor
sẽ được gọi (trước) khi đối tượng bị hủy (xóa khỏi bộ nhớ). Thực tế, ta thường sử dụng destructor để giải phóng thủ công các tài nguyên của object. Giúp bộ gom rác (Garbage Collection/GC) hoạt động nhẹ nhàng hơn.
Thông thường, những object trong bộ nhớ heap mà không được instance nào trỏ vào (null), sau một thời gian sẽ được GC tự động xóa đi. Chúng ta có thể xóa đối tượng thủ công bằng cách sử dụng Dispose Pattern.
C++ cũng có khái niệm Destructor, nhưng Java theo mình biết thì không. Đối với Java, đối tượng có phương thức object.finalize() cũng có chức năng tương tự.
1 2 3 4 5 6 7 8 9 10 11 |
class Demo { Demo() { System.Console.WriteLine("Demo's constructor is called."); } ~Demo() { System.Console.WriteLine("Demo's destructor is called."); } } |
Getter & Setter
Để kiểm soát khả năng truy cập vào nội bộ Class và đảm bảo tính bao đóng của hướng đối tượng. Chúng ta cần sử dụng Getter
và Setter
để truy cập và thao tác với các Attributes
. Trong đó:
- Attributes/Field: Thường là các biến
private
hoặcprotected
để bên ngoài không thao tác trực tiếp được. Attributes nên dùng để lưu trữ dữ liệu. Còn thao tác với nó giao choGetter
vàSetter
. - Getter và Setter: Thường là các phương thức
public
để người dùng có thể truy cập và thao tác với dữ liệu trênAttributes
.
Ví dụ đầy đủ viết bằng Java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package blog.hieuda.com; class Car { // Constructors public Car() { this("Default Name"); } public Car(String name) { setName(name); setSpeed(69); } // Attribute / Field private String name; private float speed; // Getter & Setter method for Name public String getName() { return this.name; } public void setName(String name) { this.name = name; } // Getter & Setter with valudation method for Speed. public float getSpeed() { return this.speed; } public void setSpeed(float speed) throws IllegalArgumentException { if (speed > 0) { this.speed = speed; } else { throw new IllegalArgumentException("Speed must be positive"); } } } public class Main { public static void main(String[] args) { Car myCar = new Car(); myCar.setName("Vinfast Lux"); myCar.setSpeed(69); myCar.Run(); } } |
Property của C# – Thay thế cho Get-Set truyền thống
Để tránh nhầm lẫn, anh em cần hiểu Property chính là getter và setter của C#. Thực tế, C# vẫn có thể tạo get và set method giống như Java. Nhưng sử dụng Property là tiêu chuẩn mà anh em cần nắm.
Về bản chất nó vẫn cần một field private để truyền giá trị vào. Nếu ta khai báo Property kiểu rút gọn, CLR sẽ tự động tạo một vùng nhớ ẩn để lưu trữ. Điểm khác biệt lớn nhất là nó khai báo đơn giản hơn Java và thao tác giống với một biến.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Car { // Default usage of Property public string Name { get; set; } // Property Speed with private Field and validation private float speed; public float Speed { get => speed; set => speed = value > 0 ? value : throw new ArgumentException("Speed must be positive"); } // Run a car public void Run() => Console.WriteLine($"{Name} runs at {Speed}km/h"); } static void Main(string[] args) { Car myCar = new Car(); myCar.Name = "Vinfast Lux"; myCar.Speed = 69; myCar.Run(); } |
Vài trò hay với Property trên trang Microsoft tại đây.
Tổng kết phần 1
Đọc đến đây nhiều anh em sẽ thắc mắc sao mình viết lan man cái gì vậy. Đó cũng là chủ đích của mình. Phần này mình chỉ chú trọng các thành phần mà một ngôn ngữ lập trình hướng đối tượng cần có. Xin phép để dành các nguyên tắc cần nắm cho phần sau.
Series lập trình hướng đối tượng – OOP in basic:
- Các khái niệm căn bản trong lập trình hướng đối tượng.
- Bốn tính chất cần lưu tâm khi lập trình hướng đối tượng.
- Nguyên tắc S.O.L.I.D – Cảnh giới tối thượng của hướng đối tượng.