OOP Bài 13: Thành viên tĩnh (Static Variables) - Khối tài sản chung của Class
1. Bản chất của Thuộc tính tĩnh là gì?
Nếu thuộc tính thông thường (Instance Attribute) là tài sản riêng, dữ liệu riêng của từng Đối tượng, thì Thuộc tính tĩnh (Static Attribute) là tài sản chung của toàn bộ dòng họ (Class) đó.
- Instance Attribute: Thuộc về Object. Khởi tạo bằng
new. Truy cập qua từ khóa$this(hoặc->). - Static Attribute: Thuộc về Class. Không cần khởi tạo Object vẫn dùng được. Truy cập qua từ khóa
self::hoặcTênClass::.
Góc nhìn thực chiến: Quản lý mạng lưới máy bán vé tự động (TVM) của tuyến Metro. Số lượng vé còn lại trong từng máy là tài sản riêng của máy đó (Thuộc tính thường). Nhưng Tổng số lượng máy TVM đang hoạt động trên toàn hệ thống lại là một thông tin chung mà tất cả các máy đều chia sẻ (Thuộc tính tĩnh).
2. Sự khác biệt dưới nắp capo (Góc nhìn bộ nhớ)
Khi bạn khai báo biến tĩnh (thêm từ khóa static), hệ điều hành sẽ xử lý nó hoàn toàn khác biệt so với các Object thông thường:
- Khi ứng dụng (hoặc script) vừa khởi chạy, biến tĩnh sẽ được nạp ngay vào một phân vùng RAM đặc biệt gọi là Data Segment (hoặc Static Space), độc lập hoàn toàn với Heap.
- Biến tĩnh chỉ được khởi tạo đúng một lần duy nhất.
- Dù bạn có tạo ra 1 triệu Object từ Class đó, thì cả 1 triệu Object này đều trỏ chung về một vùng RAM duy nhất chứa biến tĩnh. Nếu Object A thay đổi biến tĩnh, Object B sẽ lập tức thấy sự thay đổi đó.
3. Code minh họa: Bài toán đếm tổng số Object
Ví dụ biến tĩnh hoạt động như một "biến toàn cục có không gian tên" (Namespaced Global Variable) trong PHP:
class TicketVendingMachine {
// 1. THUỘC TÍNH TĨNH: Tài sản chung
// Đếm xem có bao nhiêu máy đang hoạt động
public static $totalActiveMachines = 0;
// 2. Thuộc tính thường: Tài sản riêng
private $machineId;
public function __construct($id) {
$this->machineId = $id;
// Mỗi khi một máy mới ra đời, nó tự động cập nhật số liệu chung
// Dùng từ khóa self:: thay vì $this->
self::$totalActiveMachines++;
}
public function __destruct() {
// Khi máy bị hủy, giảm số lượng chung xuống
self::$totalActiveMachines--;
}
}
// Bắt đầu chạy hệ thống
echo "Ban đầu: " . TicketVendingMachine::$totalActiveMachines . " máy\n"; // In ra: 0
$tvm1 = new TicketVendingMachine("BEN_THANH_01");
$tvm2 = new TicketVendingMachine("SUOI_TIEN_01");
// Không cần gọi qua Object, gọi thẳng từ Class
echo "Hiện tại: " . TicketVendingMachine::$totalActiveMachines . " máy\n"; // In ra: 2
4. Mặt trái của Static: Tại sao nó là "con dao hai lưỡi"?
Dù tiện lợi (truy cập nhanh không cần new), nhưng trong các kiến trúc Backend quy mô lớn, việc lạm dụng Static Variable bị coi là một Anti-pattern (Mẫu phản thiết kế). Vì sao?
A. Kẻ thù của Đa luồng (Race Condition)
Nếu bạn viết backend bằng Go (Golang) hoặc Java, các request được xử lý đồng thời bằng hàng ngàn luồng (Threads/Goroutines). Nếu 2 luồng cùng lúc cố gắng thay đổi một biến Static (ví dụ: bộ đếm lượt truy cập), hệ thống sẽ gặp hiện tượng Race Condition - dữ liệu ghi đè lên nhau gây sai số. Bạn sẽ phải dùng đến các cơ chế khóa (Mutex Lock) cực kỳ phức tạp để bảo vệ cái biến Static đó, làm giảm hiệu năng hệ thống thảm hại.
B. Nỗi ác mộng của Unit Test
Khi viết Unit Test, chúng ta muốn mỗi bài test (Test Case) phải chạy độc lập. Nhưng vì biến tĩnh tồn tại dai dẳng trên RAM, dữ liệu từ bài Test 1 sẽ bị "dính" (leak) sang bài Test 2, gây ra những lỗi vô lý mà debug cực kỳ tốn thời gian (Flaky Tests).
C. Thực chất nó là Biến toàn cục (Global Variable)
Triết lý của OOP là Đóng gói. Việc sinh ra biến tĩnh thực chất là đi ngược lại triết lý này, tạo ra một biến toàn cục trá hình lẩn khuất bên trong một Class.
Tóm lại: Chỉ nên dùng Static Variable cho các thông tin mang tính chất Cấu hình tĩnh, Hằng số (Constant), hoặc ứng dụng trong mẫu thiết kế cụ thể như Singleton. Tuyệt đối không dùng Static Variable để lưu trữ Trạng thái (State) có thể biến đổi trong quá trình hệ thống đang chạy.
All rights reserved