Tính đóng gói (Encapsulation) là một trong những nguyên lý cơ bản của lập trình hướng đối tượng (OOP). Nó đề cập đến việc gói gọn dữ liệu (các thuộc tính) và các phương thức (hành vi) liên quan vào trong một đối tượng duy nhất. Tính đóng gói giúp bảo vệ dữ liệu khỏi sự truy cập và thay đổi không mong muốn từ bên ngoài, đồng thời cung cấp một giao diện rõ ràng và có kiểm soát để tương tác với đối tượng.
Lợi Ích của Tính Đóng Gói
- Bảo vệ dữ liệu: Ngăn chặn sự truy cập và thay đổi dữ liệu không mong muốn từ bên ngoài.
- Tính bảo trì: Dễ dàng thay đổi và nâng cấp mã nguồn mà không ảnh hưởng đến các phần khác của chương trình.
- Tính rõ ràng: Cung cấp một giao diện rõ ràng và có kiểm soát để tương tác với đối tượng.
- Tính tái sử dụng: Các đối tượng được đóng gói có thể dễ dàng tái sử dụng trong các phần khác của chương trình hoặc trong các dự án khác.
Tính Đóng Gói trong Python
Trong Python, tính đóng gói được thực hiện bằng cách sử dụng các thuộc tính và phương thức công khai (public), bảo vệ (protected), và riêng tư (private).
Thuộc Tính và Phương Thức Công Khai (Public)
Các thuộc tính và phương thức công khai có thể được truy cập từ bất kỳ đâu, cả bên trong và bên ngoài lớp.
python
class Person:
def __init__(self, name, age):
self.name = name # Thuộc tính công khai
self.age = age # Thuộc tính công khai
def display_info(self): # Phương thức công khai
print(f"Name: {self.name}, Age: {self.age}")
# Tạo đối tượng của lớp Person
person = Person("John", 30)
# Truy cập thuộc tính và phương thức công khai
print(person.name) # Output: John
print(person.age) # Output: 30
person.display_info() # Output: Name: John, Age: 30
Thuộc Tính và Phương Thức Bảo Vệ (Protected)
Các thuộc tính và phương thức bảo vệ được định nghĩa bằng cách thêm một dấu gạch dưới _
trước tên thuộc tính hoặc phương thức. Chúng có thể được truy cập từ bên trong lớp và các lớp con, nhưng không nên truy cập từ bên ngoài lớp.
python
class Person:
def __init__(self, name, age):
self._name = name # Thuộc tính bảo vệ
self._age = age # Thuộc tính bảo vệ
def _display_info(self): # Phương thức bảo vệ
print(f"Name: {self._name}, Age: {self._age}")
# Tạo đối tượng của lớp Person
person = Person("John", 30)
# Truy cập thuộc tính và phương thức bảo vệ (không khuyến khích)
print(person._name) # Output: John
print(person._age) # Output: 30
person._display_info() # Output: Name: John, Age: 30
Thuộc Tính và Phương Thức Riêng Tư (Private)
Các thuộc tính và phương thức riêng tư được định nghĩa bằng cách thêm hai dấu gạch dưới __
trước tên thuộc tính hoặc phương thức. Chúng chỉ có thể được truy cập từ bên trong lớp và không thể truy cập từ bên ngoài lớp.
python
class Person:
def __init__(self, name, age):
self.__name = name # Thuộc tính riêng tư
self.__age = age # Thuộc tính riêng tư
def __display_info(self): # Phương thức riêng tư
print(f"Name: {self.__name}, Age: {self.__age}")
def get_info(self): # Phương thức công khai để truy cập thuộc tính riêng tư
self.__display_info()
# Tạo đối tượng của lớp Person
person = Person("John", 30)
# Truy cập thuộc tính và phương thức riêng tư (sẽ gây lỗi)
# print(person.__name) # AttributeError
# print(person.__age) # AttributeError
# person.__display_info() # AttributeError
# Truy cập thuộc tính và phương thức riêng tư thông qua phương thức công khai
person.get_info() # Output: Name: John, Age: 30
Sử Dụng Getter và Setter
Getter và setter là các phương thức được sử dụng để truy cập và cập nhật các thuộc tính riêng tư của một lớp. Chúng cung cấp một giao diện có kiểm soát để tương tác với các thuộc tính này.
Ví dụ về Getter và Setter
python
class Person:
def __init__(self, name, age):
self.__name = name # Thuộc tính riêng tư
self.__age = age # Thuộc tính riêng tư
# Getter cho thuộc tính name
def get_name(self):
return self.__name
# Setter cho thuộc tính name
def set_name(self, name):
self.__name = name
# Getter cho thuộc tính age
def get_age(self):
return self.__age
# Setter cho thuộc tính age
def set_age(self, age):
if age > 0:
self.__age = age
else:
print("Age must be positive")
# Tạo đối tượng của lớp Person
person = Person("John", 30)
# Sử dụng getter để truy cập thuộc tính riêng tư
print(person.get_name()) # Output: John
print(person.get_age()) # Output: 30
# Sử dụng setter để cập nhật thuộc tính riêng tư
person.set_name("Alice")
person.set_age(25)
print(person.get_name()) # Output: Alice
print(person.get_age()) # Output: 25
# Thử cập nhật thuộc tính age với giá trị không hợp lệ
person.set_age(-5) # Output: Age must be positive
Sử Dụng Decorator @property
Python cung cấp một cách tiện lợi để định nghĩa getter và setter bằng cách sử dụng decorator @property
. Điều này giúp mã nguồn trở nên ngắn gọn và dễ đọc hơn.
Ví dụ về @property
python
class Person:
def __init__(self, name, age):
self.__name = name # Thuộc tính riêng tư
self.__age = age # Thuộc tính riêng tư
# Getter cho thuộc tính name
@property
def name(self):
return self.__name
# Setter cho thuộc tính name
@name.setter
def name(self, name):
self.__name = name
# Getter cho thuộc tính age
@property
def age(self):
return self.__age
# Setter cho thuộc tính age
@age.setter
def age(self, age):
if age > 0:
self.__age = age
else:
print("Age must be positive")
# Tạo đối tượng của lớp Person
person = Person("John", 30)
# Sử dụng getter để truy cập thuộc tính riêng tư
print(person.name) # Output: John
print(person.age) # Output: 30
# Sử dụng setter để cập nhật thuộc tính riêng tư
person.name = "Alice"
person.age = 25
print(person.name) # Output: Alice
print(person.age) # Output: 25
# Thử cập nhật thuộc tính age với giá trị không hợp lệ
person.age = -5 # Output: Age must be positive
Tính Đóng Gói và Kế Thừa
Tính đóng gói cũng có thể được kết hợp với tính kế thừa để tạo ra các lớp con mở rộng chức năng của lớp cha mà không làm lộ các chi tiết triển khai bên trong.
Ví dụ về Tính Đóng Gói và Kế Thừa
python
class Animal:
def __init__(self, name):
self.__name = name # Thuộc tính riêng tư
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
def make_sound(self):
raise NotImplementedError("Subclass must implement abstract method")
class Dog(Animal):
def make_sound(self):
return "Bark"
class Cat(Animal):
def make_sound(self):
return "Meow"
# Tạo các đối tượng của lớp Dog và Cat
dog = Dog("Buddy")
cat = Cat("Whiskers")
# Truy cập thuộc tính name và gọi phương thức make_sound
print(dog.name) # Output: Buddy
print(dog.make_sound()) # Output: Bark
print(cat.name) # Output: Whiskers
print(cat.make_sound()) # Output: Meow
Trong ví dụ trên, lớp Animal
định nghĩa một thuộc tính riêng tư __name
và một phương thức trừu tượng make_sound
. Các lớp con Dog
và Cat
kế thừa từ lớp Animal
và triển khai phương thức make_sound
.
Kết Luận
Tính đóng gói là một nguyên lý quan trọng trong lập trình hướng đối tượng, giúp bảo vệ dữ liệu và cung cấp một giao diện rõ ràng và có kiểm soát để tương tác với đối tượng. Python cung cấp nhiều cách để thực hiện tính đóng gói, bao gồm sử dụng các thuộc tính và phương thức công khai, bảo vệ, và riêng tư, cũng như sử dụng getter và setter. Hiểu và sử dụng đúng tính đóng gói sẽ giúp bạn viết mã nguồn rõ ràng, dễ bảo trì và hiệu quả hơn. Hy vọng bài viết này đã cung cấp cho bạn một cái nhìn tổng quan và chi tiết về tính đóng gói trong Python.