Python Singleton

Năm 1994, mẫu thiết kế Singleton ra đời. Từ Sách GoF, đó là một trong bốn mẫu thiết kế sáng tạo nhằm hạn chế việc khởi tạo một lớp thành một thể hiện duy nhất. Điều thúc đẩy mẫu này là các trường hợp sử dụng như Hệ thống ghi nhật ký hoặc Cơ sở dữ liệu, nơi bạn muốn có một phiên bản điều phối viên duy nhất

Bài viết này là về những gì đối với tôi dường như là cách tiếp cận tốt nhất để triển khai mẫu thiết kế singleton trong python

TLDR. Cách tiếp cận Metaclass dường như là cách tốt nhất trong ba cách, bởi vì nó dễ kiểm tra hơn so với Decorator và tốt hơn nhiều so với Classic về khả năng đọc, khả năng sử dụng lại và khả năng kiểm tra

Singleton là một mẫu thiết kế đơn giản nhưng gây tranh cãi. Đối với một số lớp chỉ cần một phiên bản của chúng, chẳng hạn như ghi nhật ký, cài đặt, bộ đệm hoặc các đối tượng liên quan đến trình điều khiển thiết bị, việc khởi tạo nhiều phiên bản có thể gây ra nhiều lỗi không mong muốn. Do đó, ý tưởng về singleton, mà…

Xin chào mọi người và chào mừng đến với một bài viết khác về các mẫu thiết kế phần mềm. Trong bài viết này tôi muốn chia sẻ với các bạn thêm một chút về mẫu sáng tạo singleton và cách nó được sử dụng trong phần mềm. Khi chúng tôi xóa phần lý thuyết, chúng tôi sẽ xem xét một số cách để triển khai trong Python. Hãy đi sâu vào

Trong thiết kế phần mềm, mẫu đơn là một mẫu trong đó chỉ một thể hiện duy nhất của một lớp được khởi tạo và sử dụng trong toàn bộ hệ thống được thiết kế. Có một trường hợp duy nhất…

Mẫu này hạn chế việc khởi tạo một lớp cho một đối tượng. Nó là một kiểu mẫu sáng tạo và chỉ liên quan đến một lớp để tạo các phương thức và các đối tượng được chỉ định

Nó cung cấp một điểm truy cập toàn cầu cho cá thể được tạo

Làm cách nào để triển khai một lớp đơn?

Chương trình sau minh họa việc triển khai lớp singleton nơi nó in các thể hiện được tạo nhiều lần

Python đã sử dụng thuật ngữ singleton trước khi “Mẫu Singleton” được định nghĩa bởi cộng đồng mẫu thiết kế hướng đối tượng. Vì vậy, chúng ta nên bắt đầu bằng cách phân biệt một số ý nghĩa của “singleton” trong Python

  1. Một bộ có độ dài một được gọi là một singleton. Mặc dù định nghĩa này có thể khiến một số lập trình viên ngạc nhiên, nhưng nó phản ánh định nghĩa ban đầu về đơn tử trong toán học. một tập hợp chứa chính xác một phần tử. Bản thân Hướng dẫn Python đã giới thiệu cho những người mới làm quen với định nghĩa này khi chương về Cấu trúc dữ liệu của nó gọi bộ một phần tử là “singleton” và từ này tiếp tục được sử dụng theo nghĩa đó trong phần còn lại của tài liệu Python. Khi hướng dẫn về Mở rộng và Nhúng cho biết, “Để gọi hàm Python… với một đối số, hãy chuyển một bộ dữ liệu đơn”, điều đó có nghĩa là một bộ dữ liệu chứa chính xác một mục
  2. Các mô-đun là "đơn lẻ" trong Python vì
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    7 chỉ tạo một bản sao duy nhất của mỗi mô-đun; . Ví dụ: khi chương Đối tượng mô-đun của Sách hướng dẫn tham khảo API Python/C khẳng định rằng “Khởi tạo một pha tạo ra các mô-đun đơn”, điều đó có nghĩa là “mô-đun đơn” là mô-đun chỉ có một đối tượng được tạo.
  3. Một “singleton” là một thể hiện của lớp đã được gán một tên chung thông qua Mẫu đối tượng toàn cầu . Ví dụ: Câu hỏi thường gặp về lập trình Python chính thức trả lời câu hỏi “Làm cách nào để chia sẻ các biến toàn cục giữa các mô-đun?” .
  4. Các đối tượng flyweight riêng lẻ là ví dụ về Mẫu Flyweight thường được các lập trình viên Python gọi là các đối tượng "singleton". Ví dụ: một nhận xét bên trong Thư viện chuẩn
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    9 khẳng định rằng “Bộ dữ liệu trống của CPython là một bộ đơn lẻ” — có nghĩa là trình thông dịch Python chỉ tạo một đối tượng bộ dữ liệu trống duy nhất, mà bộ dữ liệu này sẽ trả về lặp đi lặp lại mỗi khi nó vượt qua độ dài bằng 0 . Một nhận xét trong
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    1 đề cập tương tự đến "bộ đơn đóng băng trống. ” Nhưng cả hai đối tượng đơn lẻ này đều không phải là một ví dụ về Mẫu Singleton của Gang of Four, bởi vì không đối tượng nào là thể hiện duy nhất của lớp của nó.
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    2 cho phép bạn tạo các bộ dữ liệu khác bên cạnh bộ dữ liệu trống và
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    3 cho phép bạn tạo các bộ cố định khác. Tương tự, các đối tượng
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    4 và
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    5 là một cặp đối tượng bay, không phải là ví dụ về Mẫu Singleton, bởi vì không phải là ví dụ duy nhất của
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    6.
  5. Cuối cùng, các lập trình viên Python trong một số trường hợp hiếm hoi thực sự có nghĩa là “Mẫu Singleton” khi họ gọi một đối tượng là “singleton”. đối tượng duy nhất được trả về bởi lớp của nó mỗi khi lớp được gọi

Thư viện chuẩn Python 2 không bao gồm ví dụ về Mẫu Singleton. Mặc dù nó có các đối tượng đơn lẻ như

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
7 và
# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
8, nhưng ngôn ngữ này đã cung cấp quyền truy cập vào chúng thông qua Mẫu đối tượng toàn cầu Pythonic hơn bằng cách đặt tên cho chúng trong mô-đun
# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
9. Nhưng các lớp học của họ không thể gọi được.

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
3

Tuy nhiên, trong Python 3, các lớp đã được nâng cấp để sử dụng Mẫu Singleton

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis

Điều này giúp cuộc sống của các lập trình viên dễ dàng hơn, những người cần một lệnh gọi nhanh luôn trả về

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
7, mặc dù những trường hợp như vậy rất hiếm. Trong hầu hết các dự án Python, các lớp này không bao giờ được gọi và lợi ích vẫn hoàn toàn là lý thuyết. Khi các lập trình viên Python cần đối tượng
# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
7, họ sử dụng Mẫu đối tượng toàn cầu và chỉ cần nhập tên của nó.

Quá trình triển khai của Băng nhóm 4 người¶

Ngôn ngữ C++ mà Gang of Four đang nhắm đến đã áp đặt một cú pháp riêng biệt cho việc tạo đối tượng, trông giống như

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]

Một dòng C++ có nội dung

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
82 luôn tạo một thể hiện lớp mới — nó không bao giờ trả về một singleton. Với sự hiện diện của cú pháp đặc biệt này, các tùy chọn của họ để cung cấp các đối tượng đơn lẻ là gì?

  1. Bang of Four không dễ dàng sử dụng Mẫu đối tượng toàn cầu vì nó không hoạt động đặc biệt hiệu quả trong các phiên bản đầu tiên . Ở đó, tất cả các tên toàn cầu đều chia sẻ một không gian tên toàn cầu đông đúc duy nhất, vì vậy các quy ước đặt tên phức tạp là cần thiết để ngăn các tên từ các thư viện khác nhau xung đột. The Gang đánh giá rằng việc thêm cả một lớp và thể hiện đơn lẻ của nó vào không gian tên toàn cầu đông đúc sẽ là quá mức. Và vì các lập trình viên C++ không thể kiểm soát thứ tự khởi tạo các đối tượng toàn cầu, nên không có đối tượng toàn cục nào có thể phụ thuộc vào việc có thể gọi bất kỳ đối tượng nào khác, do đó, trách nhiệm khởi tạo các đối tượng toàn cầu thường thuộc về mã máy khách.
  2. Không có cách nào để ghi đè ý nghĩa của
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    82 trong C++, vì vậy cần có một cú pháp thay thế nếu tất cả các máy khách đều nhận cùng một đối tượng. Tuy nhiên, ít nhất có thể tạo ra lỗi thời gian biên dịch cho mã máy khách khi gọi
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    82 bằng cách đánh dấu hàm tạo của lớp là
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    85 hoặc
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    86
  3. Vì vậy, Gang of Four xoay quanh một phương thức lớp sẽ trả về đối tượng đơn lẻ của lớp. Không giống như một hàm toàn cục, một phương thức lớp tránh thêm một tên khác vào không gian tên toàn cầu và không giống như một phương thức tĩnh, nó cũng có thể hỗ trợ các lớp con là các lớp đơn

Làm thế nào mã Python có thể minh họa cách tiếp cận của họ? . Một cách khác là đưa ra một ngoại lệ trong

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
60 để không thể khởi tạo đối tượng bình thường. Sau đó, phương thức lớp có thể sử dụng thủ thuật phương thức dunder để tạo đối tượng mà không kích hoạt ngoại lệ

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
8

Điều này thành công ngăn khách hàng tạo các phiên bản mới bằng cách gọi lớp

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
9

Thay vào đó, người gọi được hướng dẫn sử dụng phương thức lớp

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
61, phương thức này tạo và trả về một đối tượng

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
1

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
2

Các cuộc gọi tiếp theo tới

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
61 trả về singleton mà không lặp lại bước khởi tạo [như chúng ta có thể thấy từ thực tế là “Tạo phiên bản mới” không được in lại], chính xác như dự định của Nhóm Bốn Người

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
4

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
5

Có nhiều sơ đồ phức tạp hơn mà tôi có thể tưởng tượng để triển khai phương thức lớp Gang of Four ban đầu trong Python, nhưng tôi nghĩ ví dụ trên thực hiện tốt nhất việc minh họa sơ đồ ban đầu với ít phép thuật nhất có thể. Vì mẫu của Gang of Four dù sao cũng không phù hợp với Python, nên tôi sẽ chống lại sự cám dỗ để lặp lại nó nhiều hơn và thay vào đó chuyển sang cách mẫu được hỗ trợ tốt nhất trong Python

Một triển khai Pythonic hơn¶

Theo một nghĩa nào đó, Python bắt đầu được chuẩn bị tốt hơn C++ cho Mẫu Singleton, bởi vì Python thiếu từ khóa

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
82 buộc một đối tượng mới được tạo. Thay vào đó, các đối tượng được tạo bằng cách gọi một hàm có thể gọi được, điều này không áp đặt giới hạn cú pháp đối với hoạt động mà hàm có thể gọi được thực sự thực hiện.

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6

Để cho phép tác giả kiểm soát các cuộc gọi đến một lớp, Python 2. 4 đã thêm phương thức

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6 dunder để hỗ trợ các mẫu sáng tạo thay thế như Mẫu Singleton và Mẫu Flyweight .

Web có đầy đủ các công thức Mẫu Singleton có tính năng

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6, mỗi công thức đề xuất một cơ chế phức tạp hơn hoặc ít phức tạp hơn để giải quyết vấn đề lớn nhất của phương pháp. thực tế là
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
60 luôn được gọi trên giá trị trả về, cho dù đối tượng được trả về có mới hay không. Để làm cho ví dụ của riêng tôi trở nên đơn giản, tôi sẽ không định nghĩa một phương thức
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
60 và do đó tránh phải làm việc xung quanh nó

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
1

Đối tượng được tạo trong lần gọi đầu tiên đến lớp

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
2

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
3

Nhưng cuộc gọi thứ hai trả về cùng một trường hợp. Thông báo “Tạo đối tượng” không in ra, đối tượng khác cũng không được trả về

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
4

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
5

Ví dụ trên chọn sự đơn giản, với chi phí thực hiện tra cứu thuộc tính

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
68 hai lần trong trường hợp phổ biến. Đối với những lập trình viên ngại lãng phí như vậy, kết quả tất nhiên có thể được gán tên và sử dụng lại trong câu lệnh return. Và nhiều cải tiến khác có thể được tưởng tượng sẽ dẫn đến mã byte nhanh hơn. Nhưng dù được điều chỉnh công phu đến đâu, mẫu trên là cơ sở của mọi lớp Python ẩn một đối tượng đơn lẻ đằng sau những gì đọc giống như khởi tạo lớp bình thường

Bản án¶

Mặc dù Mẫu Singleton ban đầu của Gang of Four không phù hợp với một ngôn ngữ như Python thiếu các khái niệm về

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
82,
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
86 và
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
85, nhưng không dễ để loại bỏ mẫu khi nó được xây dựng trên đỉnh
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6 — xét cho cùng, những người độc thân là một phần của

Nhưng Mẫu Singleton trong Python có một số nhược điểm

Phản đối đầu tiên là việc triển khai Mẫu Singleton khiến nhiều lập trình viên Python khó đọc. Cách thay thế Mẫu đối tượng chung dễ đọc. nó chỉ đơn giản là câu lệnh gán quen thuộc, được đặt ở cấp cao nhất của mô-đun. Nhưng một lập trình viên Python lần đầu tiên đọc phương thức

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6 có lẽ sẽ phải dừng lại và tìm kiếm tài liệu để hiểu chuyện gì đang xảy ra.

Phản đối thứ hai là Mẫu Singleton thực hiện các cuộc gọi đến lớp, như

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
95, gây hiểu lầm cho người đọc. Trừ khi nhà thiết kế đã đặt “Singleton” hoặc một số gợi ý khác trong tên lớp và người đọc biết rõ các mẫu thiết kế để hiểu gợi ý, mã sẽ đọc như thể một phiên bản mới đang được tạo và trả về

Phản đối thứ ba là Mẫu Singleton buộc phải cam kết thiết kế mà Mẫu Đối tượng Toàn cầu thì không. Việc cung cấp một đối tượng toàn cầu vẫn giúp lập trình viên tự do tạo các phiên bản khác của lớp — điều này có thể đặc biệt hữu ích cho các thử nghiệm, cho phép mỗi người kiểm tra một đối tượng hoàn toàn riêng biệt mà không cần đặt lại đối tượng dùng chung trở lại trạng thái tốt đã biết. Nhưng Mẫu Singleton làm cho các trường hợp bổ sung không thể. [Trừ khi người gọi sẵn sàng khom lưng để vá khỉ; hoặc tạm thời sửa đổi

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
96 để phá vỡ logic trong
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6; hoặc tạo một lớp con thay thế phương thức. Nhưng một khuôn mẫu bạn phải giải quyết nói chung là một khuôn mẫu bạn nên tránh. ]

Vậy tại sao bạn lại sử dụng Mẫu Singleton trong Python?

Một tình huống thực sự yêu cầu mẫu sẽ là một lớp hiện có, do yêu cầu mới, giờ đây sẽ hoạt động tốt nhất như một thể hiện duy nhất. Nếu không thể di chuyển tất cả mã máy khách để ngừng gọi trực tiếp lớp và bắt đầu sử dụng một đối tượng toàn cầu, thì Mẫu Singeton sẽ là một cách tiếp cận tự nhiên để xoay vòng sang một mã đơn trong khi vẫn giữ nguyên cú pháp cũ

Singleton trong Python là gì?

Mẫu Singleton trong python là mẫu thiết kế cho phép bạn chỉ tạo một phiên bản của một lớp, trong suốt vòng đời của chương trình . Sử dụng một mẫu đơn có nhiều lợi ích. một vài trong số họ là. Để giới hạn quyền truy cập đồng thời vào tài nguyên được chia sẻ. Để tạo một điểm truy cập toàn cầu cho một tài nguyên.

Python có cần singleton không?

Các lập trình viên Python hầu như không bao giờ triển khai Mẫu Singleton như được mô tả trong cuốn sách Gang of Four, trong đó lớp Singleton cấm khởi tạo thông thường và thay vào đó cung cấp một phương thức lớp trả về thể hiện singleton.

Tại sao chúng ta nên tránh singleton?

Singletons cản trở thử nghiệm đơn vị . Singleton có thể gây ra sự cố khi viết mã có thể kiểm tra nếu đối tượng và các phương thức được liên kết với nó quá chặt chẽ đến mức không thể kiểm tra nếu không viết một lớp đầy đủ chức năng dành riêng cho Singleton.

Tại sao tôi lại sử dụng một singleton?

Một singleton nên được sử dụng khi quản lý quyền truy cập vào tài nguyên được chia sẻ bởi toàn bộ ứng dụng và nó sẽ có khả năng phá hoại . Đảm bảo rằng quyền truy cập vào chuỗi tài nguyên được chia sẻ an toàn là một ví dụ rất hay về nơi loại mẫu này có thể quan trọng.

Chủ Đề