CodeGym /Các khóa học /C# SELF /Lỗi với các modifier truy cập và đóng gói (encapsulation)...

Lỗi với các modifier truy cập và đóng gói (encapsulation)

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

Các modifier truy cập giống như hàng rào và ổ khóa trong nhà bạn: chúng quyết định ai có thể vào phòng nào, ai chỉ được nhìn qua lỗ khóa thôi. Nhắc lại, trong C# có các loại sau:

Modifier Ai thấy được
public
Tất cả mọi người
private
Chỉ bên trong class hiện tại
protected
Class hiện tại và các class con (kế thừa)
internal
Toàn bộ project (assembly)
protected internal
Toàn bộ project + các class con
private protected
Chỉ các class con trong cùng project

Nhiệm vụ của đóng gói (encapsulation) là giấu chi tiết thực thi của class, để không ai có thể phá object của bạn bằng dòng obj.Field = 99999; nếu bạn không muốn thế.

2. Những lỗi kinh điển với mức truy cập

Mở hết mọi thứ bằng public

Lỗi phổ biến nhất là khai báo tất cả field và method của class là public. Lý do thường là: "Biết đâu sau này cần? Để người khác truy cập member của mình ở đâu cũng được!". Cách này tưởng tiện, cho đến khi ai đó gán cho object của bạn một trạng thái không hợp lệ, hoặc bắt đầu dùng chi tiết bên trong khiến bạn sợ sửa code.

Ví dụ lỗi:


public class BankAccount
{
    public string Owner;
    public decimal Balance;
}

Bây giờ ai cũng có thể làm thế này:


var acc = new BankAccount();
acc.Owner = "";
acc.Balance = -1000000M; // Đột nhiên, nợ một triệu!

Có gì sai?
Bạn đã phá vỡ mọi nguyên tắc an toàn và logic. Dù ngân hàng của bạn chỉ chứa lá cây hay thẻ Monopoly, số dư âm "tùy thích" vẫn rất kỳ cục.

Biến class thành "structure không có cấu trúc"

Lỗi thứ hai là làm public không chỉ field mà cả property và method bên trong, vốn không nên cho bên ngoài truy cập.


public class Vault
{
    public string DoorPIN = "1234";
    public void OpenDoor() 
    {
        // Một chút phép thuật
    }
}

Giờ thì sao? Ai cũng biết PIN và mở cửa két của bạn. Dù chỉ là "két test", một tháng sau ai đó quên mất lỗ hổng này, và chuyện xấu xảy ra.

Cách đúng:
Interface của class nên tối giản và bảo vệ. Cái gì cần cho bên ngoài — để public. Cái gì chỉ dùng nội bộ — private. Nếu cần cho class con — protected.

3. Lỗi đóng gói (encapsulation)

Trong .NET có quy tắc nghiêm: không field nào của class (trạng thái) được để public! Tất cả phải đóng lại hoặc ít nhất bọc qua property. Truy cập trực tiếp field là kiểu code xấu kinh điển và nguồn gốc của bug khó tìm.

Tại sao field nên là private

Field là nền tảng trạng thái bên trong object. Nếu cho public, bạn mất kiểm soát khi nào và giá trị gì được gán vào. Object của bạn dễ bị giá trị sai, và khi đổi format, mọi truy cập public sẽ hỏng hết.

Thay vào đó:
Khai báo field là private, chỉ expose property hoặc method cần thiết:


public class BankAccount
{
    private decimal balance;

    public decimal Balance
    {
        get { return balance; }
        private set 
        {
            if (value < 0) 
                throw new ArgumentException("Số dư không được âm");
            balance = value;
        }
    }

    public BankAccount(decimal initialBalance)
    {
        Balance = initialBalance;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("Không thể nạp số tiền bằng 0 hoặc âm!");
        Balance += amount;
    }
}

Lỗi nặng: public set cho dữ liệu không nên thay đổi

Đôi khi chỉ cần get, nhưng dev lại viết public { get; set; } "cho nhanh". Kết quả là ai cũng có thể làm:


account.Balance = 99999999; // Sao lại không nhỉ?

Đúng hơn là:
Nếu property chỉ nên set trong class, hãy để set có modifier:


public decimal Balance { get; private set; }

hoặc, nếu chỉ set khi tạo object (C# 14):


public decimal Balance { get; init; }

4. Khi protected cũng là bẫy

Mở cửa cho tất cả class con

protected là cách tốt để truyền chức năng cho class con, nhưng có dữ liệu không nên cho cả class con truy cập! Ví dụ, nếu bạn không muốn con (class kế thừa) phá field quan trọng.

Ví dụ:


public class SecureVault
{
    protected string secretCode = "1234";
}

Giờ class con nào cũng có thể:


public class HackerVault : SecureVault
{
    public void Hack()
    {
        secretCode = "0000"; // Đổi mã dễ như chơi!
    }
}

Khuyến nghị:
Chỉ dùng protected cho thứ thực sự cần cho class con. Còn lại cứ để private, nếu cần thì viết method protected.

5. Lỗi với internal và lẫn lộn assembly

Modifier internal nghe hấp dẫn: "cho mọi thứ truy cập trong project". Nhưng khi project lớn hơn một file hoặc dùng thư viện, sẽ có xung đột. Nhiều bạn mới học vô tình để cái gì đó public trong assembly, dù class nên giấu hoàn toàn.

Ví dụ:


internal class Logger
{
    internal void Write(string msg) { /* ... */ }
}

Giờ ai cũng gọi Logger.Write từ bất kỳ file nào trong project. Nếu sau này bạn add DLL này vào project khác? Tất cả "bếp núc" sẽ lộ ra nếu bạn để public.

Mẹo:
Dùng internal cho class kỹ thuật, nhưng chi tiết nhạy cảm vẫn nên private.

6. Thực hành: ứng dụng ngân hàng

Tiếp tục ví dụ app ngân hàng, để thực tế thấy dễ sai và cách sửa.

Ví dụ lỗi #1: Field public


public class BankAccount
{
    public decimal Balance;
    public string Owner;
}

Vấn đề: ai cũng có thể phá.

Sửa bằng property


public class BankAccount
{
    private decimal _balance;
    private string _owner;

    public string Owner => _owner; // Chỉ đọc

    public decimal Balance 
    { 
        get => _balance; 
        private set 
        {
            if (value < 0) throw new ArgumentException("Số dư không được âm");
            _balance = value;
        }
    }

    public BankAccount(string owner, decimal initialBalance)
    {
        if (string.IsNullOrWhiteSpace(owner))
            throw new ArgumentException("Tên chủ tài khoản là bắt buộc");

        _owner = owner;
        Balance = initialBalance;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Số tiền nạp phải dương");
        Balance += amount;
    }
}

Giờ nếu bạn thử thế này:


var acc = new BankAccount("Lena", 1000m);
acc.Balance = -700m; // Lỗi! set là private.

Hoặc thế này:


acc.Owner = "Hacker"; // Lỗi biên dịch, không có set

— đều không được. Chỉ qua constructor hoặc method thôi.

Lỗi: Property "cho đẹp"

Nhiều người viết:


public int Age { get; set; }

Dù tuổi chỉ nên thay đổi qua method IncreaseAge (ví dụ, ngày 1/1), không phải trực tiếp.

7. Nhắc nhở trực quan: không nên và nên

graph TD
    A[Public Fields] -->|Any code| D[Uncontrolled state]
    B[Private Fields + Public Methods] -->|Well-defined| E[Controlled state]
    F[Public Setters] -->|Anyone can change| D
    G[Private Setters] -->|Only class| E
Cách làm Bảo vệ trạng thái? Có validate được không? Dễ mở rộng?
Public fields ❌ ❌ ❌
Properties (get/set public) ❌ Một phần Có, nhưng không an toàn
Private fields + property set private ✅ ✅ ✅

8. Mẹo về style khi làm việc với đóng gói

  • Chỉ expose ra ngoài cái gì thực sự cần cho user của class.
  • Luôn validate dữ liệu vào object.
  • Đừng thừa: nếu property không nên thay đổi từ ngoài — để set là private.
  • Logic đặc biệt — luôn dùng method, không truy cập trực tiếp.
  • Dù rất muốn — đừng làm public field "cho test".
  • Dùng property thay vì field public kể cả cho đọc — dễ nâng cấp sau này.
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Lỗi về mức truy cập và đóng gói (encapsulation)
Lỗi về mức truy cập và đóng gói (encapsulation)
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Triển khai tính đóng gói và bảo vệ trạng thái của class
Triển khai tính đóng gói và bảo vệ trạng thái của class
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION