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
-
Hiểu rủi ro của việc để lộ dữ liệu (public fields) và khái niệm Đóng gói (Encapsulation).
-
Sử dụng các Access Modifiers (
public,private) để bảo vệ dữ liệu. -
Làm chủ Properties (
getvà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
-
Biến (Fields) chứa dữ liệu cốt lõi luôn luôn nên đặt là
private. -
Dùng Properties (
get/set) với từ khóapublicđể mở đường cho bên ngoài giao tiếp với biếnprivateđó. -
Khối
setcó thể chứa câu lệnhif-elseđể chặn các dữ liệu rác, độc hại (như tuổi âm, chuỗi rỗng). -
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:
-
Tạo một field
private int age. -
Tạo một Property
public int Ageđể quản lý việc đọc/ghi field trên. -
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ị. -
Ở 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