+1

OOP Bài 12: Phạm vi truy cập (Access Modifiers): public, private, protected

1. Ý nghĩa kiến trúc: Giao diện (Interface) vs Triển khai nội bộ (Implementation)

Khi bạn thiết kế một Class, bạn phải chia nó làm 2 phần rạch ròi:

  • Phần muốn cho thế giới bên ngoài dùng (Public Interface): Là những cái nút bấm trên máy bán vé. Khách hàng chỉ cần biết bấm vào là ra vé, không cần biết bên trong máy chạy thế nào.
  • Phần muốn giấu đi (Internal Implementation): Là các bo mạch, két đựng tiền, cơ chế mã hóa. Khách hàng tuyệt đối không được chạm vào.

Access Modifiers chính là các từ khóa để dán nhãn cho 2 phần này.


2. Phân tích 3 cấp độ bảo mật cốt lõi

A. public (Công khai) - "Mặt tiền cửa hàng"

  • Bản chất: Bất kỳ đoạn code nào, ở bất kỳ đâu trong dự án đều có quyền gọi hoặc sửa đổi thành viên được đánh dấu public.
  • Thực chiến: Thường chỉ dùng cho Phương thức (Methods) đóng vai trò là giao tiếp (API) của Object. Rất hiếm khi (và không nên) dùng public cho Thuộc tính.

B. private (Tuyệt mật) - "Két sắt nội bộ"

  • Bản chất: Chỉ có các phương thức nằm bên trong chính Class đó mới được phép truy cập. Mọi nỗ lực truy cập từ bên ngoài (kể cả từ các Class con kế thừa nó) đều bị trình biên dịch hoặc runtime báo lỗi ngay lập tức.
  • Thực chiến: 99% các Thuộc tính (Attributes) nên để là private để bảo vệ tính toàn vẹn dữ liệu. Nếu bên ngoài muốn đọc dữ liệu, hãy cung cấp các hàm public get...().

C. protected (Bảo mật gia truyền) - "Khu vực nội bộ nhân viên"

  • Bản chất: Các thành viên protected bị giấu kín với thế giới bên ngoài (giống hệt private). Nhưng có một ngoại lệ: Các Class con kế thừa (Child Class) từ Class cha này sẽ được phép truy cập.
  • Thực chiến: Dùng khi thiết kế các kiến trúc Kế thừa. Ví dụ, bạn viết một Base Class chung, chứa các logic tiện ích mà chỉ các Sub-Class cụ thể mới được dùng.

3. Code thực chiến: Phân định ranh giới

Ví dụ mô phỏng hệ thống cổng xoay ga tàu điện (Turnstile Gate) để thấy cách 3 modifier này hoạt động:

// --- CLASS CHA ---
class BaseAfcDevice {
    // 1. PUBLIC: Bất cứ ai cũng có thể đọc ID thiết bị
    public $deviceId; 

    // 2. PRIVATE: Chìa khóa mã hóa cấp thấp, tuyệt đối không cho ai đụng vào
    // Thậm chí Class con cũng KHÔNG được biết chìa khóa này.
    private $masterKey; 

    // 3. PROTECTED: IP Server Trung tâm
    // Bên ngoài không được biết, nhưng Class con cần biết để gửi dữ liệu
    protected $serverIp;

    public function __construct($id) {
        $this->deviceId = $id;
        $this->masterKey = "SECRET_12345"; 
        $this->serverIp = "192.168.1.100";
    }

    // Một hàm private nội bộ: Chỉ chạy bên trong BaseAfcDevice
    private function logCoreSys() {
        echo "Ghi log nhân hệ thống với key: {$this->masterKey}\n";
    }
}

// --- CLASS CON (Kế thừa từ BaseAfcDevice) ---
class TurnstileGate extends BaseAfcDevice {
    
    public function syncData() {
        // HỢP LỆ: Truy cập thuộc tính public của cha
        echo "Gate {$this->deviceId} đang đồng bộ...\n";
        
        // HỢP LỆ: Truy cập thuộc tính protected của cha
        echo "Gửi packet tới máy chủ IP: {$this->serverIp}\n";

        // LỖI CHẾT NGƯỜI (Fatal Error): Không thể truy cập private của cha
        // echo "Khóa mã hóa là: {$this->masterKey}"; // BỊ CẤM!
        // $this->logCoreSys(); // BỊ CẤM!
    }
}

// --- CODE BÊN NGOÀI (Client Code) ---
$gate = new TurnstileGate("GATE_BEN_THANH");

// ✅ HỢP LỆ (Public)
echo "Tên thiết bị: " . $gate->deviceId . "\n";

// ❌ LỖI (Protected) - Không thể chọc vào từ bên ngoài
// echo $gate->serverIp; // Fatal error: Cannot access protected property

// ❌ LỖI (Private) - Càng không thể
// echo $gate->masterKey; // Fatal error: Cannot access private property

4. Quy tắc vàng trong thiết kế hệ thống (Best Practices)

Để không tạo ra những hệ thống mã nguồn "thủng lỗ chỗ" và khó bảo trì, hãy áp dụng nguyên tắc "Mặc định đóng kín" (Default to restrictive):

  1. Bước 1: Khi vừa khai báo một Thuộc tính hoặc Phương thức mới, hãy luôn luôn đặt nó là private. Đừng nghĩ ngợi gì thêm.
  2. Bước 2: Nếu phát hiện ra có một Class con (Child Class) cần tái sử dụng một logic hoặc dữ liệu nào đó từ Class cha, hãy nâng cấp nó lên protected.
  3. Bước 3: Chỉ khi nào bắt buộc một Object khác bên ngoài phải gọi đến hàm này (ví dụ: các luồng controller gọi vào service), bạn mới mở cửa và nâng nó lên public.

Kiểm soát chặt chẽ Access Modifiers là cách rẻ nhất và hiệu quả nhất để ngăn chặn hàng ngàn con bug sinh ra do dữ liệu bị sửa đổi sai ngữ cảnh trong các dự án quy mô lớn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí