CodeGym /Các khóa học /C# SELF /Đóng gói (Encapsulation) và các modifier truy cập

Đóng gói (Encapsulation) và các modifier truy cập

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

1. Giới thiệu

Hãy tưởng tượng bạn đang thiết kế một chiếc ô tô hiện đại. Bên trong là hàng trăm chi tiết phức tạp, điện tử nhạy cảm, các tinh chỉnh tỉ mỉ. Rõ ràng là không ai nên tự tiện tiếp cận bộ điều khiển động cơ và vặn lung tung vài con ốc. Nếu ai cũng chọc vào những chỗ không nên, xe có thể hoạt động bất thường, và bạn sẽ phải kiểm tra xem ai đã phá hỏng cái gì.

Lập trình cũng vậy thôi. Khi bạn tạo một class, bạn muốn bảo vệ phần bên trong khỏi bàn tay người ngoài — để không ai vô tình làm hỏng dữ liệu quan trọng hoặc can thiệp vào các method mà thế giới bên ngoài không nên biết. Đó chính là ý nghĩa của đóng gói (encapsulation) — một trong ba nguyên lý cốt lõi của lập trình hướng đối tượng.

Đóng gói cho phép bạn giấu chi tiết cài đặt và xác định rõ cái gì "bên ngoài" có thể truy cập, còn cái gì phải giữ kín bên trong. Giống như bạn chỉ cho người dùng mở cốp xe, còn nắp capo thì khóa kỹ. Class tự quyết định dữ liệu nào sẽ show ra ngoài, dữ liệu nào giữ bí mật. Nhờ vậy, code trở nên đáng tin cậy, logic và dễ bảo trì hơn nhiều.

Nói nghiêm túc hơn, đóng gói là cách gom dữ liệu (field) và hành vi (method) liên quan thành một cấu trúc duy nhất, đồng thời giới hạn truy cập trực tiếp vào các thành phần bên trong object.

2. Modifier truy cập: bảo vệ lãnh thổ của mình

Trong C# (và các ngôn ngữ OOP khác), đóng gói được thực hiện nhờ modifier truy cập. Đây là những "nhãn" xác định thành viên nào của class (field, method, property, v.v.) có thể truy cập từ bên ngoài, còn cái nào chỉ dùng bên trong.

Chúng ta đã gặp chúng rồi, nhưng cùng nhắc lại và mở rộng thêm danh sách modifier:

Modifier Truy cập được...
public
Mọi nơi! Bất kỳ code nào trong project (và cả ngoài project nếu là thư viện)
private
Chỉ bên trong chính class đó
protected
Từ class này và tất cả class con kế thừa
internal
Chỉ trong phạm vi assembly (project) hiện tại
protected internal
Hoặc từ assembly hiện tại, hoặc từ class con kế thừa
private protected
Chỉ từ class con bên trong assembly hiện tại

Trong bài này, mình sẽ tập trung vào những cái hay dùng nhất: public, private và nhắc nhẹ về protected thôi cho đỡ nặng đầu.

Phân biệt bên trong và bên ngoài

Thử viết một class Dog nhé:


public class Dog
{
    public string Name;
    public int Age;

    public void Bark()
    {
        Console.WriteLine($"{Name} nói: Gâu!");
    }
}

Ở đây tất cả field và method đều khai báo với modifier public. Nghĩa là ai cũng có thể đổi tên hoặc tuổi của chó:

Dog rex = new Dog("Rex", 5);
rex.Name = "Sharik"; // Đột ngột đổi tên!
rex.Age = -999;     // Tuổi âm siêu dị

Nhưng field Name và Age là dữ liệu cực kỳ quan trọng, đâu thể để ai muốn làm gì thì làm.

Đừng làm như thế này nhé

Nếu để tất cả field public, bạn sẽ phá vỡ logic của class: bất kỳ ai cũng có thể làm object trở nên không hợp lệ. Ví dụ, đặt tuổi chó là số âm hoặc đặt tên kiểu "%$#!??".

3. Giấu field: dùng private

Thông thường, field của class nên để private (modifier private). Nghĩa là chỉ có thể thay đổi giá trị từ bên trong class, bên ngoài thì bó tay.


public class Dog
{
    private string name;
    private int age;

    public void Bark()
    {
        Console.WriteLine($"{name} nói: Gâu!");
    }
}

Giờ thử truy cập field từ bên ngoài:

Dog rex = new Dog("Rex", 5);
rex.name = "Sharik";  // Lỗi biên dịch

Compiler sẽ báo ngay: "Không có quyền truy cập!".

Tại sao phải cứng nhắc vậy? Thế linh hoạt đâu?

Tất cả sự "linh hoạt" sẽ được đảm bảo nhờ các method đặc biệt hoặc property (sẽ nói kỹ ở bài sau), cho phép kiểm soát truy cập và thay đổi dữ liệu theo quy tắc nhất định.

4. Đóng gói thực tế: ví dụ kiểm soát dữ liệu

Giả sử bạn muốn chó không thể có tuổi âm:


public class Dog
{
    private string name;
    private int age;

    public Dog(string name, int age)
    {
        this.name = name;
        if (age >= 0)
            this.age = age;
        else
            this.age = 0; // Không cho đặt tuổi "kỳ lạ"
    }

    public void Bark()
    {
        Console.WriteLine($"{name} nói: Gâu!");
    }

    // Method để thay đổi tuổi an toàn
    public void SetAge(int newAge)
    {
        if (newAge >= 0)
            age = newAge;
        // Có thể thêm else: báo giá trị không hợp lệ
    }
}

Giờ thì không ai bên ngoài có thể phá field trực tiếp nữa. Muốn đổi tuổi phải qua method kiểm tra hợp lệ.

5. Field vs method truy cập (getter/setter)

Cách này — để field private và cung cấp method để thao tác — gọi là đóng gói dữ liệu (data encapsulation). Để đọc giá trị field thường có method getter, để ghi thì có setter.


public class Dog
{
    private string name;

    public Dog(string name)
    {
        this.name = name;
    }

    public string GetName()
    {
        return name;
    }

    public void SetName(string newName)
    {
        // Có thể kiểm tra tên hợp lệ ở đây
        name = newName;
    }
}

Nhưng! Khi C# có property (properties), kiểu method này ít dùng hơn — property giúp code gọn và tiện hơn nhiều (chi tiết ở bài sau nhé).

6. Modifier truy cập cho method và class

Field chưa phải tất cả đâu! Modifier truy cập còn dùng cho method (function thành viên class) và thậm chí cả class luôn.

Method chỉ dùng bên trong class (ví dụ, function phụ cho logic nội bộ) thì nên để private.

Method public — chính là interface của class (không nhầm với từ khóa interface nhé), tức là cái mà người dùng class sẽ dùng.

7. Lỗi thường gặp khi làm việc với đóng gói

Lỗi số 1: cái gì cũng khai báo public.
Nghĩ rằng càng nhiều thứ truy cập được thì càng dễ dùng. Thực tế là bạn mở toang phần bên trong class, những thứ không nên bị thay đổi từ ngoài. Code kiểu này rất dễ bị lỗi và khó kiểm soát, nhất là trong project lớn.

Lỗi số 2: đổi modifier truy cập làm hỏng code bên ngoài.
Khi refactor, bạn có thể vô tình đổi modifier của method hoặc field, và toàn bộ code khác dùng nó sẽ không biên dịch được hoặc chạy sai. Đặc biệt nguy hiểm với public API.

Lỗi số 3: nhầm lẫn giữa biến local và field của class.
Đôi khi dev quên rằng biến khai báo trong method chỉ sống trong method đó. Field của class thì dùng được ở mọi method. Điều này dễ gây lỗi khó hiểu, nhất là khi tên biến trùng nhau.

Lỗi số 4: bỏ qua private và protected.
Nhiều bạn sợ dùng truy cập hạn chế, lo sau này không truy cập được. Nhưng đóng gói là để giấu những thứ không cần thiết, chỉ show ra ngoài cái thực sự cần thôi.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Kiểm soát truy cập thông qua phương thức
Kiểm soát truy cập thông qua phương thức
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sử dụng tính đóng gói với kiểm tra tính hợp lệ của dữ liệu
Sử dụng tính đóng gói với kiểm tra tính hợp lệ của dữ liệu
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION