0

Bài 11: Tính Đóng gói (Encapsulation) & Sức mạnh của Properties (get/set)

🎯 Mục tiêu bài học

  1. Hiểu rủi ro của việc để lộ dữ liệu (public fields) và khái niệm Đóng gói (Encapsulation).

  2. Sử dụng các Access Modifiers (public, private) để bảo vệ dữ liệu.

  3. Làm chủ Properties (get và set) - tính năng đặc trưng và mạnh mẽ nhất của C# để kiểm soát luồng dữ liệu.

📖 Phần 1: Tại sao phải "Đóng gói" dữ liệu?

Ở Bài 10, bạn dùng từ khóa public cho tất cả các fields (public string status;). Việc này rất nguy hiểm trong thực tế. Nếu biến là public, bất kỳ đoạn code nào bên ngoài cũng có thể chọc thẳng vào và thay đổi giá trị của nó một cách vô lý mà Class không thể ngăn cản.

C#

Server s = new Server("DB", "10.0.0.1");
s.status = "Bị hack rôi!!!"; // Dữ liệu bị ghi đè vô tội vạ vì biến là public

Tính Đóng gói (Encapsulation) ra đời để giải quyết vấn đề này với nguyên tắc: Che giấu trạng thái bên trong và bắt buộc giao tiếp qua các kênh chính thức. Thay vì dùng public, ta sẽ đổi các fields thành private (chỉ code bên trong nội bộ Class đó mới được phép đụng tới).

📖 Phần 2: C# Properties - Kênh giao tiếp chính thức

Ở các ngôn ngữ khác như Java, khi biến bị khóa bằng private, bạn phải viết 2 hàm cồng kềnh (ví dụ SetStatus() và GetStatus()) để bên ngoài có thể đọc/ghi dữ liệu một cách có kiểm soát. Với C#, Microsoft tạo ra Properties (Thuộc tính) để làm việc này một cách thanh lịch hơn rất nhiều thông qua 2 khối lệnh get (lấy ra) và set (gán vào).

1. Full Property (Kiểm soát logic chặt chẽ)

Dùng khi bạn muốn thêm điều kiện kiểm tra trước khi cho phép gán dữ liệu.

C#

class Product
{
    // Field thực sự chứa dữ liệu bị khóa kín (private, thường viết thường chữ cái đầu)
    private double price; 

    // Property đóng vai trò là "người gác cổng" (public, thường Viết Hoa chữ cái đầu)
    public double Price 
    {
        get 
        { 
            return price; // Cho phép bên ngoài đọc giá trị
        }
        set 
        { 
            // Từ khóa 'value' đại diện cho giá trị mà bên ngoài đang cố gắng gán vào
            if (value >= 0)
            {
                price = value; 
            }
            else
            {
                Console.WriteLine("Loi: Gia san pham khong the la so am!");
            }
        }
    }
}

// Bên ngoài sử dụng:
Product p = new Product();
p.Price = -5000;  // Chạy vào khối set -> Bị báo lỗi, dữ liệu price thật vẫn an toàn
p.Price = 150000; // Chạy vào khối set -> Hợp lệ, price được cập nhật
Console.WriteLine(p.Price); // Chạy vào khối get -> In ra 150000

2. Auto-implemented Property (Viết tắt siêu nhanh)

Nếu bạn chỉ muốn Đóng gói dữ liệu mà không cần logic kiểm tra phức tạp nào bên trong, C# cho phép viết tắt hoàn toàn. Trình biên dịch sẽ tự động tạo ra biến private ngầm ở bên dưới.

C#

class User
{
    // Ngắn gọn, không cần khai báo field private thủ công
    public string Username { get; set; }
    
    // Chỉ cho phép bên ngoài đọc (get), không cho phép sửa (private set)
    public string Role { get; private set; } 
    
    public User(string name)
    {
        Username = name;
        Role = "Admin"; // Trong nội bộ Class thì sửa được Role thoải mái
    }
}

💻 Phần 3: Thực hành

Chúng ta sẽ tạo một Class BankAccount áp dụng Đóng gói để bảo vệ số dư tài khoản, tuyệt đối không cho phép ai gán số dư trực tiếp.

C#

using System;

namespace Lesson11
{
    class BankAccount
    {
        // Dữ liệu nhạy cảm phải được bảo vệ
        private double balance;

        // Property chỉ có 'get', không có 'set' -> Bên ngoài chỉ được xem số dư, cấm sửa đổi trực tiếp
        public double Balance 
        {
            get { return balance; }
        }

        public string AccountName { get; set; } // Auto-property cho tên tài khoản

        public BankAccount(string name, double initialBalance)
        {
            AccountName = name;
            balance = initialBalance;
        }

        // Kênh duy nhất để thay đổi số dư là thông qua các hàm nghiệp vụ
        public void Deposit(double amount)
        {
            if (amount > 0)
            {
                balance += amount;
                Console.WriteLine($"[Nap tien] +{amount:N0}. So du moi: {balance:N0}");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            BankAccount acc = new BankAccount("Hieu Backend", 500000);
            
            Console.WriteLine($"So du hien tai: {acc.Balance:N0}");

            // acc.Balance = 1000000000; -> Dòng này sẽ báo lỗi đỏ ngay lập tức vì Property Balance không có 'set'
            
            acc.Deposit(200000); // Cách hợp lệ duy nhất để tăng tiền

            Console.ReadLine();
        }
    }
}

📝 Tóm tắt bài học

  1. Biến (Fields) chứa dữ liệu cốt lõi luôn luôn nên đặt là private.

  2. Dùng Properties (get/set) với từ khóa public để mở đường cho bên ngoài giao tiếp với biến private đó.

  3. Khối set có thể chứa câu lệnh if-else để chặn các dữ liệu rác, độc hại (như tuổi âm, chuỗi rỗng).

  4. Dùng Auto-implemented Property (public int Age { get; set; }) khi không cần logic kiểm tra cầu kỳ để code sạch và gọn.

🛠 Bài tập thực hành

Yêu cầu: Xây dựng Class Employee (Nhân viên) quản lý tuổi:

  1. Tạo một field private int age.

  2. Tạo một Property public int Age để quản lý việc đọc/ghi field trên.

  3. Ràng buộc trong khối set: Tuổi của nhân viên chỉ được phép gán nằm trong khoảng từ 18 đến 60. Nếu người dùng cố tình gán số ngoài khoảng này (ví dụ 15 hoặc 65), hãy in ra dòng cảnh báo "Tuổi không hợp lệ" và không cập nhật giá trị.

  4. Ở hàm Main, khởi tạo một nhân viên và thử gán các mức tuổi khác nhau để test logic


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í