Ý tưởng
Singleton Pattern là một mẫu thiết kế tạo ra một đối tượng duy nhất của một lớp và cung cấp một điểm truy cập toàn cầu để truy xuất đến đối tượng đó.
Lớp Singleton khai báo một phương thức tĩnh getInstance
để trả về một phiên bản duy nhất của lớp đó.
Hàm tạo của Singleton phải được ẩn đi để mã khách hàng không thể sử dụng toán tử new
để tạo đối tượng.
Tất cả các triển khai của Singleton đều bao gồm hai bước chung sau:
- Đặt hàm tạo mặc định thành riêng tư để ngăn các đối tượng khác sử dụng toán tử
new
của lớp Singleton. - Tạo một phương thức tạo tĩnh như một hàm tạo. Phương thức này sẽ “bí mật” gọi hàm tạo riêng tư để tạo đối tượng và lưu trữ nó trong một biến thành viên tĩnh. Từ đó, mọi lần gọi phương thức này sẽ trả về đối tượng được lưu trữ trong biến thành viên.
Nếu mã của bạn có thể truy cập vào lớp Singleton, nó có thể gọi phương thức tĩnh của lớp Singleton. Bất kể khi nào phương thức này được gọi, nó luôn trả về cùng một đối tượng.
Ưu điểm của Singleton Pattern:
- ✔ Bạn có thể đảm bảo rằng một lớp chỉ có một phiên bản.
- ✔ Bạn có một điểm truy cập toàn cầu để truy xuất đến phiên bản đó.
- ✔ Chỉ khởi tạo đối tượng khi lần đầu tiên yêu cầu.
Nhược điểm của Singleton Pattern:
- ❌ Vi phạm nguyên tắc đơn trách nhiệm (single responsbility). Mẫu này giải quyết cùng lúc hai vấn đề.
- ❌ Singleton Pattern có thể che giấu thiết kế kém chất lượng, ví dụ như các thành phần trong chương trình biết quá nhiều về nhau.
- ❌ Mẫu này cần xử lý đặc biệt trong môi trường đa luồng để tránh việc tạo nhiều đối tượng Singleton bởi nhiều luồng cùng một lúc.
- ❌ Việc kiểm thử đơn vị cho mã khách của Singleton có thể khó khăn, vì nhiều framework kiểm thử tạo đối tượng giả bằng cách kế thừa. Vì hàm tạo của lớp Singleton là riêng tư và hầu hết các ngôn ngữ không cho phép ghi đè phương thức tĩnh, bạn cần nghĩ ra cách khác để tạo đối tượng giả. Bạn có thể không viết mã kiểm thử hoặc không sử dụng Singleton Pattern.
Ứng dụng
- Nếu một lớp trong chương trình chỉ có một phiên bản duy nhất và chỉ có thể sử dụng cho tất cả các khách hàng, bạn có thể sử dụng Singleton Pattern.
⚡ Singleton Pattern ngăn chặn việc tạo đối tượng của lớp trừ khi sử dụng phương thức tạo đặc biệt. Phương thức này có thể tạo một đối tượng mới, nhưng nếu đối tượng đã được tạo, nó sẽ trả về đối tượng hiện có. - Nếu bạn cần kiểm soát biến toàn cầu một cách nghiêm ngặt, bạn có thể sử dụng Singleton Pattern.
⚡ Singleton Pattern khác biệt với biến toàn cầu, nó đảm bảo chỉ có một phiên bản của lớp tồn tại. Ngoài lớp Singleton, không có cách nào thay thế phiên bản được lưu trữ.
Lưu ý rằng bạn có thể điều chỉnh giới hạn và số lượng phiên bản Singleton bằng cách chỉnh sửa phương thức getInstance
trong mã.
Ví dụ, một số trình quản lý tài nguyên thường được thiết kế theo Singleton Pattern.
Trong hệ thống máy tính, các tài nguyên cần quản lý bao gồm các tài nguyên bên ngoài phần mềm, ví dụ như mỗi máy tính có thể có nhiều máy in, nhưng chỉ có một Printer Spooler để tránh việc hai công việc in cùng lúc.
Mỗi máy tính có thể có nhiều cổng giao tiếp, hệ thống phải quản lý tập trung các cổng giao tiếp này để tránh một cổng giao tiếp bị gọi đồng thời bởi hai yêu cầu. Trình quản lý nhiệm vụ khó khăn để khởi động hai tác vụ giống nhau.
Cấu trúc
- Lớp Singleton khai báo một phương thức tĩnh
getInstance
để trả về một phiên bản duy nhất của lớp đó.- Hàm tạo của Singleton phải được ẩn đi để mã khách không thể sử dụng toán tử
new
để tạo đối tượng.
- Hàm tạo của Singleton phải được ẩn đi để mã khách không thể sử dụng toán tử
Pseudocode
Trong ví dụ này, lớp kết nối cơ sở dữ liệu là một Singleton.
Lớp này không cung cấp hàm tạo công khai, vì vậy cách duy nhất để lấy đối tượng là thông qua phương thức getInstance
. Phương thức này sẽ lưu trữ đối tượng được tạo lần đầu tiên và trả về nó cho tất cả các lần gọi sau đó.
Ví dụ
Ví dụ sử dụng: Nhiều nhà phát triển xem mẫu Singleton là một anti-pattern. Do đó, tần suất sử dụng nó trong mã Java đang dần giảm.
Tuy nhiên, vẫn có một số ví dụ về Singleton trong thư viện lõi của Java:
Cách nhận biết: Singleton có thể được nhận biết thông qua phương thức tạo tĩnh trả về cùng một đối tượng được lưu trữ trong bộ nhớ cache.
Lớp kết nối cơ sở dữ liệu
Lớp kết nối cơ sở dữ liệu là một ví dụ về Singleton.
Lớp này không cung cấp hàm tạo công khai, vì vậy cách duy nhất để lấy đối tượng là thông qua phương thức getInstance
. Phương thức này sẽ lưu trữ đối tượng được tạo lần đầu tiên và trả về nó cho tất cả các lần gọi sau đó.
Lazy Singleton (Singleton Lười)
Ý tưởng triển khai Lazy Singleton là: Lười không tìm kiếm, lười không muốn khởi tạo chính mình.
instance
không được khởi tạo ban đầu, chỉ khi lần đầu tiên gọi getInstance()
thì đối tượng mới được tạo.
Nhược điểm: Khi có hai luồng gọi getInstance()
cùng một lúc và cả hai đều thực hiện đến dòng mã if (null == instance)
, instance
sẽ là null
.
Tiếp tục thực thi, sẽ tạo ra hai đối tượng, vi phạm mục đích ban đầu của Singleton.
Double-Checked Locking (Kiểm tra khóa hai lần)
Nếu không muốn tạo ra chi phí khi không cần lấy đối tượng và không muốn gặp vấn đề an toàn luồng, có thể sử dụng kiểm tra khóa hai lần.
Lưu ý: Tại sao lại có một lần kiểm tra
instance
trong khối đồng bộ?Điều này là do nếu
instance
lànull
và hai luồng cùng gọigetInstance()
, do cơ chếsynchronized
, chỉ cho phép một luồng đi vào, luồng còn lại phải chờ đợi.Trong trường hợp này, nếu không có lần kiểm tra
instance
thứ hai, có thể xảy ra tình huống luồng đầu tiên tạo một đối tượng và luồng thứ hai cũng tạo một đối tượng.
Mối quan hệ với các mẫu thiết kế khác
- Lớp Facade Pattern thường có thể được chuyển đổi thành một lớp Singleton Pattern, vì trong hầu hết các trường hợp, một đối tượng facade là đủ.
- Nếu bạn có thể giảm bớt tất cả các trạng thái chia sẻ của đối tượng thành một đối tượng flyweight, thì Flyweight Pattern và Singleton Pattern sẽ trở nên tương tự. Tuy nhiên, hai mẫu này có hai khác biệt cơ bản.
- Chỉ có một thực thể Singleton, trong khi lớp flyweight có thể có nhiều thực thể và có thể có trạng thái nội tại khác nhau.
- Đối tượng Singleton có thể thay đổi, trong khi đối tượng flyweight là không thay đổi.
- Abstract Factory Pattern, Builder Pattern và Prototype Pattern đều có thể được triển khai bằng Singleton Pattern.