Thuộc tính riêng tư
Thuộc phần Lập trình hướng đối tượng trong hành trình Python của Coddy. Bài học 15 trên 64.
Các thuộc tính riêng tư dùng dấu gạch dưới để cho biết không nên truy cập trực tiếp vào một số dữ liệu từ bên ngoài lớp.
Tại sao chúng ta cần thuộc tính riêng tư? Hãy tưởng tượng bạn có một tài khoản ngân hàng. Bạn không muốn ai đó trực tiếp thay đổi số dư của mình. Họ nên thực hiện việc đó qua các kênh phù hợp (phương thức nạp tiền/rút tiền) để ngân hàng có thể xác thực giao dịch, kiểm tra gian lận, lưu hồ sơ, v.v. Thuộc tính riêng tư cũng hoạt động như vậy, chúng bảo vệ dữ liệu của bạn khỏi bị thay đổi không chính xác.
Đây là ví dụ dùng dấu gạch dưới đơn (quy ước cho “dùng nội bộ”):
class Person:
def __init__(self, name, age):
self._name = name # "protected": chỉ dùng nội bộ
self._age = age # "protected": chỉ dùng nội bộBạn vẫn có thể truy cập các thuộc tính có một dấu gạch dưới, nhưng đó là tín hiệu cho biết bạn không nên làm vậy:
person = Person("Alice", 30)
print(person._name) # Vẫn chạy, nhưng không nên dùngDùng hai dấu gạch dưới để tăng tính riêng tư (biến đổi tên):
class Person:
def __init__(self, name, age):
self.__name = name # "private": tên bị biến đổi
self.__age = age # "private": tên bị biến đổi
def get_name(self):
return self.__name
def set_age(self, age):
if age >= 0:
self.__age = age
else:
print("Age must be positive!")Tại sao nên dùng các phương thức getter và setter? Chúng giống như những người gác cổng. Thay vì để bất kỳ ai thay đổi trực tiếp __age (điều này có thể dẫn đến tuổi âm hoặc dữ liệu không hợp lệ khác), ta buộc họ dùng set_age(), phương thức này sẽ kiểm tra đầu vào trước. Cách này ngăn lỗi và bảo đảm tính toàn vẹn của dữ liệu.
“Phần code thêm” có mục đích riêng:
get_name()- Kiểm soát quyền đọc tênset_age(age)- Kiểm tra tuổi trước khi cho phép thay đổi (không có tuổi âm!)- Nếu không có các phương thức này, ai đó có thể đặt
age = -100và làm hỏng chương trình của bạn
Dùng các phương thức truy cập để tương tác với các thuộc tính riêng tư:
person = Person("Bob", 25)
print(person.get_name()) # Bob
person.set_age(30) # Hợp lệ: tuổi thành 30
person.set_age(-5) # Không hợp lệ: tuổi phải là số dương! (tuổi vẫn là 30)Bạn thấy lợi ích chưa? Phương thức set_age() ngăn dữ liệu không hợp lệ. Nếu không có nó, bạn có thể vô tình tạo một người có tuổi là -5, điều đó chẳng hợp lý chút nào!
Các thuộc tính có hai dấu gạch dưới sẽ bị biến đổi tên, nhưng bạn vẫn có thể truy cập chúng:
person = Person("Charlie", 35)
# Cách này không hoạt động:
# print(person.__name) # AttributeError
# Nhưng cách này hoạt động (không nên dùng):
print(person._Person__name) # CharlieVí dụ thực tế: Tại sao cần viết tất cả đoạn code này?
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Riêng tư: không thể thay đổi trực tiếp
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def get_balance(self):
return self.__balance
# Không có thuộc tính (attribute) riêng tư:
# account.__balance = -1000000 # Tai hại! Cho phép số dư âm
# Có thuộc tính riêng tư:
account = BankAccount(100)
account.deposit(50) # An toàn: đã kiểm tra giá trị
print(account.get_balance()) # 150“Đoạn code bổ sung” (các phương thức) bảo vệ dữ liệu của bạn khỏi những thay đổi không hợp lệ. Nó giống như có nhân viên bảo vệ thay vì để tài sản quý giá của bạn không được bảo vệ.
Đầu ra:
Alice
Bob
Age must be positive!
Charlie
150Gạch dưới đơn _attribute theo quy ước có nghĩa là “chỉ dùng nội bộ”. Gạch dưới kép __attribute kích hoạt cơ chế đổi tên (name mangling) để tăng tính riêng tư. Hãy dùng các phương thức getter/setter (phương thức truy cập) để tương tác đúng cách với dữ liệu riêng tư và thêm bước xác thực. “Đoạn code bổ sung” giúp ngăn lỗi bằng cách đảm bảo dữ liệu luôn hợp lệ.
Thử thách
DễHoàn thiện lớp User trong user.py với chức năng mật khẩu riêng tư, sau đó dùng lớp này trong driver.py. Làm theo các chú thích TODO để triển khai các phương thức kiểm tra và thay đổi mật khẩu.
Tự làm thử
# TODO: Nhập lớp (class) User từ module user
# TODO: Lấy test case từ đầu vào
test_case = input()
if test_case == "constructor_test":
# TODO: Tạo đối tượng (object) User với mật khẩu ban đầu là "secure123"
user = User("secure123")
print("User created successfully")
print("Initial password set")
elif test_case == "correct_password_test":
# TODO: Tạo đối tượng (object) User với mật khẩu ban đầu là "secure123"
user = User("secure123")
# TODO: Kiểm tra phương thức (method) check_password bằng mật khẩu đúng
# In ra "Password check successful!" nếu phương thức trả về True
# In ra "Password check failed!" nếu phương thức trả về False
result = user.check_password("secure123")
if result:
print("Password check successful!")
else:
print("Password check failed!")
elif test_case == "incorrect_password_test":
# TODO: Tạo đối tượng (object) User với mật khẩu ban đầu là "secure123"
user = User("secure123")
# TODO: Kiểm tra phương thức check_password bằng mật khẩu sai "wrong"
# In ra "Incorrect password rejected!" nếu phương thức trả về False
# In ra "Security issue: incorrect password accepted!" nếu phương thức trả về True
result = user.check_password("wrong")
if not result:
print("Incorrect password rejected!")
else:
print("Security issue: incorrect password accepted!")
elif test_case == "change_password_test":
# TODO: Tạo đối tượng (object) User với mật khẩu ban đầu là "secure123"
user = User("secure123")
# TODO: Kiểm tra việc đổi mật khẩu từ "secure123" sang "newpass456"
# In ra "Password changed successfully!" nếu phương thức trả về True
# In ra "Password change failed!" nếu phương thức trả về False
result = user.change_password("secure123", "newpass456")
if result:
print("Password changed successfully!")
else:
print("Password change failed!")
# TODO: Kiểm tra mật khẩu mới "newpass456" có hoạt động không
# In ra "New password works!" nếu phương thức trả về True
# In ra "New password doesn't work!" nếu phương thức trả về False
if user.check_password("newpass456"):
print("New password works!")
else:
print("New password doesn't work!")
elif test_case == "change_password_wrong_old_test":
# TODO: Tạo đối tượng (object) User với mật khẩu ban đầu là "secure123"
user = User("secure123")
# TODO: Thử đổi mật khẩu bằng mật khẩu cũ không đúng
# In ra "Security working: incorrect old password rejected!" nếu phương thức trả về False
# In ra "Security issue: password changed with wrong old password!" nếu phương thức trả về True
result = user.change_password("wrong", "hackerpw")
if not result:
print("Security working: incorrect old password rejected!")
else:
print("Security issue: password changed with wrong old password!")
elif test_case == "comprehensive_test":
# TODO: Tạo đối tượng (object) User với mật khẩu ban đầu là "secure123"
user = User("secure123")
print("User created with initial password")
# TODO: Kiểm tra phương thức (method) check_password bằng mật khẩu đúng
if user.check_password("secure123"):
print("Initial password verification successful")
# TODO: Kiểm tra phương thức check_password bằng mật khẩu không đúng
if not user.check_password("wrongpass"):
print("Incorrect password properly rejected")
# TODO: Kiểm tra việc đổi mật khẩu từ "secure123" sang "newpass456"
if user.change_password("secure123", "newpass456"):
print("Password successfully changed")
# TODO: Kiểm tra mật khẩu cũ không còn dùng được
if not user.check_password("secure123"):
print("Old password no longer works")
# TODO: Kiểm tra mật khẩu mới dùng được
if user.check_password("newpass456"):
print("New password works correctly")
# TODO: Thử đổi mật khẩu bằng mật khẩu cũ không đúng
if not user.change_password("wrongold", "hackerpw"):
print("Security maintained: wrong old password rejected")
# TODO: Kiểm tra mật khẩu vẫn an toàn sau lần đổi mật khẩu không thành công
if user.check_password("newpass456"):
print("Password remains secure after failed change attempt")
else:
print("Unknown test case")Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTham số selfPhương thứcThuộc tínhPhương thức khởi tạo (__init__)Ôn tập: máy tính đơn giản4Kế thừa
Kế thừa cơ bảnHàm super()Ghi đè phương thứcKế thừa đa lớpThứ tự phân giải phương thứcÔn tập: hệ thống phân cấp nhân viên2Decorator
Giới thiệu về decoratorDecorator thuộc tínhDecorator phương thức tĩnhDecorator phương thức lớp5Tính đa hình
Xem lại cách ghi đè phương thứcKiểu gõ vịtLớp và phương thức trừu tượngThiết kế giao diệnÔn tập: máy tính hình học8Các khái niệm OOP nâng cao
Kết hợp và kế thừaMixinPhương thức tĩnh và phương thức lớpDecorator lớpTrình quản lý ngữ cảnhTự luyện tập: Trình biên dịch Python online