Tính đóng gói – Encapsulation trong lập trình hướng đối tượng

Ở bài viết này, mình sẽ đi sâu hơn nữa vào tính đóng gói – encapsulation trong lập trình hướng đối tượng với ví dụ bằng ngôn ngữ lập trình Java.

Mình sẽ làm rõ tính đóng gói bằng cách trả lời 3 câu hỏi sau:

  • Nó là gì?
  • Nó dùng để làm gì?
  • Sử dụng nó như thế nào?

Bản thân mình khi học một cái gì đó thì mình sẽ tự đặt ra những câu hỏi như thế này và sẽ từ từ giải đáp nó trong quá trình mình tiếp thu kiến thức. hãy chịu khó đặt ra những câu hỏi trong suốt quá trình học bạn nhé!

Cụ thể trong bài viết này, mình sẽ đề cập đến những vấn đề sau:

Ok, giờ thì “nổ máy” thôi:

1. Đóng gói là gì?

Định nghĩa: Đóng gói là nguyên lý lập trình hướng đối tượng nhằm tổ chức và bảo vệ dữ liệu bằng cách gom nhóm các thuộc tính và hành vi liên quan vào cùng một thực thể (class), đồng thời kiểm soát quyền truy cập đến dữ liệu bên trong.
Điều này giúp che giấu chi tiết triển khai, đảm bảo tính toàn vẹn của dữ liệu, giảm phụ thuộc giữa các thành phần và tăng khả năng bảo trì, mở rộng phần mềm.

Mở rộng ra hơn một chút, thì việc gom nhóm các thông tin liên quan ở đây nó còn là việc bạn đưa các class liên quan vào cùng package nữa cơ!


Một ví dụ nho nhỏ như sau, bạn có một chiếc điện thoại:

  • Bạn chỉ cần bấm nút gọi, là điện thoại sẽ thực hiện cuộc gọi.
  • Bạn không cần truy cập trực tiếp vào vi mạch điện tử bên trong đúng không?
  • Về gom nhóm, thì tất cả các linh kiện nó nằm trong cái điện thoại đúng không nào?

Điện thoại đã “đóng gói” toàn bộ logic thực hiện cuộc gọi phức tạp bên trong, chỉ cung cấp một nút bấm gọi đơn giản đúng không nào.

2. Đóng gói để làm gì?

Tính đóng gói giúp bảo vệ dữ liệu, đảm bảo rằng đối tượng chỉ được thao tác theo cách mà lập trình viên cho phép

Mục đích chính của đóng gói:

  • Bảo mật thông tin bên trong object (tránh bị chỉnh sửa ngoài ý muốn)
  • Ngăn lỗi logic do thao tác trực tiếp sai từ bên ngoài
  • Kiểm soát thay đổi, dễ dàng nâng cấp hoặc validate dữ liệu

Hãy đặt ra thêm những câu hỏi nhé:

  • Bảo mật thông tin như thế nào? bảo vệ khỏi ai hay cái gì?
  • Ngăn lỗi logic do thao tác trực tiếp từ bên ngoài là như thế nào? Chả lẽ người dùng lại trực tiếp thao tác từ bên ngoài à?
  • Thế đóng gói dễ dàng nâng cấp hoặc validate dư liệu kiểu gì, cơ chế nào?

Mình sẽ trả lời ngay đây:

  • Việc đóng gói sẽ giúp bạn bảo vệ độ toàn vẹn dữ liệu của một lớp A với một lớp B, ngăn chặn không cho lớp B trực tiếp thay đổi dữ liệu của lớp A.
  • Việc trực tiếp thao tác ở đây là đề cập đến việc lớp B truy cập (lấy ra hoặc gán – get/set) giá trị cho thuộc tính trong lớp A mà không thông qua phương thức lớp A cung cấp – Ở đây là lấy ra hoặc gán bằng dot notation (Anh em hay gọi nó là “chấm ra thuộc tính”). Và tại sao nó lại có lỗi logic, ví dụ trước khi gán dữ liệu cho thuộc tính thì bạn phải qua xử lý đi, nếu gán trực tiếp thì kiểu gì cũng có lỗi.
  • Áp dụng đóng gói dễ dàng nâng cấp hoặc validate hơn là đúng, Đóng gói cho phép chúng ta ẩn dữ liệu và buộc mọi thao tác với dữ liệu phải đi qua các phương thức có kiểm soát (getter/setter hoặc method logic). Nhờ đó, nếu muốn validate dữ liệu hoặc thay đổi logic xử lý, ta chỉ cần sửa đúng một chỗ duy nhất trong class, thay vì sửa ở mọi nơi đang sử dụng biến đó. (Cái này có liên quan đến một nguyên tắc nữa là DRY – Don’t Repeat Yourself)

3. Sử dụng đóng gói như thế nào?

Ok giờ đến phần “làm đóng gói như thế nào?”:

Đầu tiên thì mình xin khẳng định, cốt lõi để thực hiện đóng gói là giới hạn khả năng truy cập của thuộc tính và cung cấp phương thức giúp truy cập đến thuộc tính.
Nói ngắn gọn thì cốt lõi của đóng gói chính là Access modifierphương thức getter/setter.

Nếu bạn chưa nắm rõ về access modifier, bạn có thể đọc nó tại đây: link

Về phần phương thức getter/setter, thì nó chỉ là những function bình thường, và được đặt tên hàm theo quy ước (convention) là get cái gì đó để lấy ra giá trị của thuộc tính đó, và set cái gì đó để gán giá trị cho thuộc tính đó trong lớp

Nhấn mạnh nhá, nó là những function bình thường, và người ta có quy ước đặt tên cho nó thôi

Nếu bạn nào đã code Javascript, và đọc phần bên trên tự nhiên thắc mắc rằng “Javascript không có access modifier nhưng vẫn hỗ trợ OOP và đóng gói mà!”
Mình xin trả lời rằng: với Javascript, từ ES6 trở lên đã hỗ trợ access modifier, và concept của Javascript lúc này mới hướng tới OOP. Trước ES6 thì concept của Javascript là Prototype base và nó không hoàn toàn được thiết kế xoay quay OOP, và trước ES6 thì chúng ta giấu thuộc tính bằng cách giấu tên thật của thuộc tính (closure)

Để thực hiện đóng gói thì chúng ta cần làm những bước sau:

  1. Giới hạn truy cập cho thuộc tính của class bằng access modifier – thường là để các thuộc tính là private, chỉ cho phép truy cập trong class.
  2. Tạo các hàm/phương thức để truy cập – thường là getter/setter

Đơn giản nhể!

Code ví dụ nhé, giờ mình sẽ tạo một class Car:

public class Car {      // Lớp xe ô tô
    private String name;        // Tên dòng xe
    private double price;       // Giá tiền
    private int numberOfSeat;   // Số ghế

    public void setName(String carName) {       // Gán giá trị cho thuộc tính name
        this.name = carName;
    }

    public void setPrice(double sellPrice) {    // Gán giá trị cho thuộc tính price
        if (sellPrice < 0) {    // Kiểm tra sellPrice truyền vào trước khi gán
            System.out.println("Giá không được nhỏ hơn 0");
        } else {
            this.price = sellPrice;
        }
    }

    public void setNumberOfSeat(int noOfSeat) { // Gán giá trị cho thuộc tính numberOfSeat
        if (noOfSeat < 1) { // Kiểm tra noOfSeat truyền vào trước khi gán
            System.out.println("Số chỗ ngồi không thể nhỏ hơn 1!");
        } else {
            this.numberOfSeat = noOfSeat;
        }
    }

    public String getName() {       // Lấy ra giá trị của name
        return this.name;
    }

    public double getPrice() {      // Lấy ra giá trị của price
        return this.price;
    }

    public int getNumberOfSeat() {  // Lấy ra giá trị của numberOfSeat
        return this.numberOfSeat;
    }
}

Ở đoạn code trên bạn có thể thấy những điều sau:

  • Toàn bộ thuộc tính và hành vi liên quan đến một chiếc xe hơi (Car) được gom vào cùng một class
  • Ba thuộc tính name, price, và numberOfSeat được khai báo là private, không thể truy cập trực tiếp từ bên ngoài lớp.
  • Các phương thức getset được cung cấp có kiểm soát, cho phép truy xuất hoặc thay đổi dữ liệu một cách gián tiếp và có điều kiện
  • Trước khi gán giá trị, chương trình kiểm tra dữ liệu hợp lệ → giúp ngăn lỗi logic và đảm bảo tính toàn vẹn dữ liệu (data integrity)

4. Kết luận

Tính đóng gói không chỉ là một khái niệm “lý thuyết suông” trong OOP – mà là kỹ thuật cực kỳ quan trọng để xây dựng phần mềm an toàn, dễ bảo trì, và mở rộng được trong thực tế.

Mình sẽ để lại một vài điểm mấu chốt (key learning point) của đóng gói:

  • Đóng gói để che giấu chi tiết triển khai
  • Đóng gói đảm bảo độ toàn vẹn dữ liệu
  • Đóng gói liên quan trực tiếp đến Access modifier và Getter/Setter

Tạm thời thế đã, bạn có thể đọc tiếp về tính kế thừa

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Lên đầu trang