Python - Lớp Singleton

Xin chào các bạn nhà lập trình nhí! Hôm nay, chúng ta sẽ bắt đầu hành trình hấp dẫn vào thế giới lập trình Python. Đích đến của chúng ta là đất nước thiên nhiên của các Lớp Singleton! Đừng lo lắng nếu điều này có vẻ gây sợ – tôi hứa rằng bằng hết bài học này, các bạn sẽ trở thành một người đài Singleton. Hãy bắt đầu!

Python - Singleton Class

Lớp Singleton là gì?

Trước khi bắt đầu lập trình, hãy hiểu rõ lớp Singleton là gì. Hãy tưởng tượng bạn đang chơi một trò game, và chỉ có một nhân vật chơi mà bạn kiểm soát suốt trò chơi. Dù bạn lưu và tải lại trò chơi bao nhiêu lần, bạn luôn kiểm soát cùng một nhân vật. Đó chính là điều gì một lớp Singleton làm trong lập trình – nó đảm bảo rằng một lớp chỉ có một thể hiện, và cung cấp một điểm truy cập toàn cục vào nó.

Tạo các Lớp Singleton trong Python

Trong Python, có nhiều cách để tạo một lớp Singleton. Chúng ta sẽ khám phá hai phương pháp chính: sử dụng __init__ và sử dụng __new__. Nhưng trước hết, hãy xem tại sao chúng ta cần một lớp Singleton.

Tại sao sử dụng Singleton?

Singletons rất hữu ích khi bạn muốn đảm bảo rằng chỉ một thể hiện của một lớp tồn tại suốt chương trình của bạn. Điều này có thể giúp ích cho:

  1. Quản lý trạng thái toàn cục
  2. Đồng bộ hóa hành động trên hệ thống
  3. Quản lý tài nguyên chia sẻ, như kết nối cơ sở dữ liệu

Bây giờ, hãy nhấc tay vào việc lập trình!

Sử dụng init

Phương pháp đầu tiên của chúng ta liên quan đến việc sử dụng phương thức __init__, được gọi khi một đối tượng được tạo. Dưới đây là cách chúng ta có thể tạo một Singleton bằng cách sử dụng __init__:

class Singleton:
_instance = None

def __init__(self):
if Singleton._instance is None:
Singleton._instance = self
else:
raise Exception("This class is a singleton!")

@staticmethod
def get_instance():
if Singleton._instance is None:
Singleton()
return Singleton._instance

# Sử dụng
s1 = Singleton.get_instance()
s2 = Singleton.get_instance()
print(s1 is s2)  # Output: True

# Điều này sẽ gây ra ngoại lệ
# s3 = Singleton()

Hãy phân tích:

  1. Chúng ta định nghĩa một biến lớp _instance để giữ chúng ta thể hiện duy nhất.
  2. Trong phương thức __init__, chúng ta kiểm tra xem thể hiện đã tồn tại hay chưa. Nếu không, chúng ta tạo một. Nếu có, chúng ta gây ra ngoại lệ.
  3. Chúng ta cung cấp phương thức get_instance() để truy cập Singleton của chúng ta. Phương thức này tạo một thể hiện nếu chưa có, hoặc trả về thể hiện hiện tại.

Khi chạy mã này, s1s2 sẽ là cùng một thể hiện. Việc tạo thể hiện mới trực tiếp (như s3 = Singleton()) sẽ gây ra ngoại lệ.

Sử dụng new

Bây giờ, hãy xem phương pháp khác sử dụng __new__. Phương thức này được gọi trước __init__ khi tạo thể hiện mới.

class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

# Sử dụng
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)  # Output: True

Đây là điều gì đang xảy ra:

  1. Chúng ta ghi đè phương thức __new__, phụ trách tạo và trả về thể hiện mới.
  2. Nếu _instance là None, chúng ta tạo thể hiện mới bằng cách sử dụng super().__new__(cls).
  3. Chúng ta luôn trả về _instance, dù nó là mới tạo hoặc đã tồn tại.

Phương pháp này ngắn gọn hơn và không cần phương thức get_instance() riêng.

So sánh hai phương pháp

Hãy so sánh các phương pháp trong bảng dưới đây:

Phương pháp Ưu điểm Nhược điểm
__init__ - Kiểm soát rõ ràng hơn
- Có thể ngăn chặn khởi tạo trực tiếp
- Cần phương thức get_instance() riêng
- Chỉnh sửa phức tạp hơn
__new__ - Ngắn gọn hơn
- Hoạt động với khởi tạo trực tiếp
- Kiểm soát ít rõ ràng hơn
- Có thể ít dễ hiểu cho người mới bắt đầu

Cả hai phương pháp đạt được mục tiêu giống nhau, vì vậy lựa chọn thường phụ thuộc vào sở thích cá nhân hoặc yêu cầu cụ thể của dự án của bạn.

Ví dụ thực tế

Để kết thúc, hãy xem một ví dụ thực tế. Tưởng tượng chúng ta đang tạo một trò game, và chúng ta muốn đảm bảo rằng chỉ có một nhân vật chơi:

class PlayerCharacter:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.name = "Hero"
cls._instance.health = 100
cls._instance.level = 1
return cls._instance

def level_up(self):
self.level += 1
print(f"{self.name} đã đạt cấp độ {self.level}!")

# Sử dụng
player1 = PlayerCharacter()
player2 = PlayerCharacter()

print(player1.name)  # Output: Hero
print(player2.name)  # Output: Hero

player1.level_up()  # Output: Hero đã đạt cấp độ 2!
print(player2.level)  # Output: 2

Trong ví dụ này, dù bạn tạo bao nhiêu PlayerCharacter, bạn luôn nhận được cùng một thể hiện. Điều này đảm bảo rằng trò chơi của bạn chỉ có một nhân vật chơi, duy trì trạng thái nhất quán suốt trò chơi.

Và thế là đã! Bạn vừa đã thành thạo kỹ năng tạo các lớp Singleton trong Python. Hãy nhớ, như bất kỳ công cụ mạnh mẽ nào, hãy sử dụng Singleton một cách khôn ngoan. Chúng rất có ích cho việc quản lý trạng thái toàn cục hoặc tài nguyên chia sẻ, nhưng sử dụng quá mức có thể làm cho mã của bạn khó kiểm tra và duy trì hơn.

Hãy tiếp tục tập luyện, tiếp tục lập trình, và nhất quán là hãy luôn vui chơi! Chào đến lần sau, chúc mừng lập trình!

Credits: Image by storyset