OOP Bài 8: Constructor (Hàm tạo): Khái niệm, Phân loại và Vòng đời
1. Khái niệm: "Tiếng khóc chào đời" của Object
Constructor (Hàm tạo) là một phương thức cực kỳ đặc biệt bên trong Class. Đặc điểm nhận diện của nó là:
- Được tự động gọi duy nhất một lần ngay khi từ khóa
newđược thực thi (đối với các ngôn ngữ như PHP, C++, Java). - Tên của nó thường trùng với tên Class (C++, Java) hoặc dùng một từ khóa cố định như
__construct()(trong PHP). - Không bao giờ trả về giá trị (No return type): Nhiệm vụ của nó không phải là tính toán ra một kết quả, mà là thiết lập Trạng thái ban đầu (Initial State).
Bản chất thực chiến: Nếu Class là bản vẽ, Object là cái máy AFC vừa được lắp ráp xong phần cứng, thì Constructor chính là bước "cắm điện và nạp firmware". Không có Constructor, Object chỉ là một cục RAM vô tri chứa toàn giá trị rác hoặc
null.
2. Vòng đời của Object (Object Lifecycle) và vị trí của Constructor
Để hiểu Constructor, bạn cần nhìn vào toàn bộ vòng đời của một Object từ lúc sinh ra đến lúc chết đi. Vòng đời này trải qua 4 giai đoạn:
- Allocation (Cấp phát bộ nhớ): OS tìm một vùng trống trên Heap RAM đủ để chứa Object.
- Initialization (Khởi tạo - CONSTRUCTOR CHẠY Ở ĐÂY): Constructor được kích hoạt. Nó bơm dữ liệu vào các thuộc tính, mở kết nối DB, hoặc mở file. Lúc này Object chính thức "sống".
- In Use (Sử dụng): Hệ thống gọi các Hành vi (Methods) của Object để xử lý nghiệp vụ (
tapIn(),deductFare()). - Destruction (Hủy diệt): Khi không còn ai dùng Object nữa, hàm Hủy (Destructor) sẽ chạy để nhả tài nguyên, sau đó vùng RAM bị Garbage Collector (GC) thu hồi.
3. Phân loại Constructor (Tổng quan)
Có 3 loại Constructor chính mà một Backend Developer phải nắm rõ:
- Default Constructor (Hàm tạo mặc định): Không nhận vào bất kỳ tham số nào. It tự thiết lập các giá trị mặc định, hard-code sẵn bên trong.
- Parameterized Constructor (Hàm tạo có tham số): Nhận dữ liệu từ bên ngoài truyền vào lúc
new. Đây là loại được dùng 99% trong thực tế để bơm cấu hình hoặc các Object phụ thuộc (Dependency Injection) vào Object chính. - Copy Constructor (Hàm tạo sao chép): Rất quan trọng trong C++. Dùng để tạo ra một Object mới bằng cách clone (sao chép) y hệt Trạng thái của một Object có sẵn.
4. Best Practice sống còn: Những điều cấm kỵ trong Constructor
Nhiều lập trình viên mới thường nhồi nhét mọi thứ vào Constructor. Tuy nhiên, dưới góc độ thiết kế hệ thống chuẩn SOLID, bạn cần nhớ quy tắc vàng này: "Constructor is for Assignment, not for Heavy Lifting" (Constructor dùng để gán dữ liệu, không phải để làm việc nặng).
❌ Bad Code (Nhét I/O nặng vào Constructor):
class DeviceMonitor {
private $dbConnection;
private $deviceData;
public function __construct($deviceId) {
// CẤM KỴ: Mở connection DB ngay trong lúc khởi tạo
$this->dbConnection = new PDO('mysql:host=localhost;dbname=afc_system', 'root', '');
// CẤM KỴ: Query API hoặc DB nặng trong lúc khởi tạo
$stmt = $this->dbConnection->query("SELECT * FROM devices WHERE id = $deviceId");
$this->deviceData = $stmt->fetch();
}
}
Tác hại: Mỗi lần gọi
new DeviceMonitor(), chương trình sẽ bị treo (block) một nhịp để chờ DB. Khi bạn muốn viết Unit Test cho Class này, nó sẽ ném ra lỗi vì không có DB thật để kết nối. Class này đã bị gắn chặt (tight coupling) với cơ sở dữ liệu.
✅ Good Code (Chỉ nhận Dependencies - Chuẩn Clean Architecture):
class DeviceMonitor {
private $dbConnection;
private $deviceId;
// Chỉ việc nhận DB Connection đã được khởi tạo TỪ TRƯỚC (Dependency Injection)
public function __construct(PDO $dbConnection, $deviceId) {
$this->dbConnection = $dbConnection;
$this->deviceId = $deviceId;
}
// Logic truy vấn DB nặng nên nằm ở một hàm riêng, gọi khi cần thiết
public function loadDeviceData() {
$stmt = $this->dbConnection->query("SELECT * FROM devices WHERE id = {$this->deviceId}");
return $stmt->fetch();
}
}
Lợi ích: Khởi tạo Object nhanh như chớp. Khi test, bạn chỉ cần truyền vào một DB "giả" (Mock DB). Kiến trúc hệ thống trở nên cực kỳ linh hoạt và dễ bảo trì.
All rights reserved