# 73: Chống lại sự cám dỗ của Singleton Pattern

Singleton Pattern giải quyết được rất nhiều vấn đề. Bạn biết bạn chỉ cần một instance(**) duy nhất. Bạn có thể đảm bảo rằng instance này được khởi tạo trước khi nó được sử dụng. Nó giữ cho thiết kế của bạn đơn giản bằng một điểm truy cập toàn cầu. Tất cả đều tốt. Vậy điều gì khiến thiết kế pattern cổ điển này trở nên không được yêu thích?

Nhiều trường hợp, một kết quả bất ngờ sẽ đến. Nó có thể rất hấp dẫn, nhưng kinh nghiệm chỉ ra rằng, hầu hết các Singleton Pattern này hại nhiều hơn lợi. Chúng cản trở khả năng kiểm tra và bảo trì. Thật không may, sự bổ sung khôn ngoan không được sử dụng phổ biến và các lập trình viên không thể bỏ qua việc sử dụng các Singleton.

Nhưng điều này rất đáng để dừng lại:

Các yêu cầu single-instance thường được tính đến. Trong nhiều trường hợp, các suy đoán đơn giản rằng những instance sẽ cần thêm vào trong tương lai. Nêu ra các tính năng trên lý thuyết về thiết kế của một ứng dụng sẽ ảnh hưởng tiêu cực tại một số vị trí. Yêu cầu sẽ cần thay đổi. Thiết kế tốt bao gồm điều này nhưng các Singleton thì không.

Singleton cũng âm thầm gây ra sự phụ thuộc giữa các đơn vị code riêng lẻ. Đây là vấn đề cả bởi vì chúng bị ẩn đi và vì chúng liên kết các các đơn vị không cần thiết. Code của bạn sẽ trở nên “khó nuốt” khi bạn cố gắng viết cái đơn vị kiểm tra, điều này phụ thuộc vào các kết nối lỏng lẻo và khả năng thay thế có chọn lọc các đối tượng mô phỏng trên môi trường thực tế.

Singleton ngăn chặn các đối tượng mô phỏng này.

Singleton cũng kéo dài ngầm trong hệ thống, điều này một lần nữa cản trở các đơn vị kiểm tra, thứ phụ thuộc vào thành phần độc lập, do đó việc kiểm tra có thể được thực hiện theo bất kỳ thứ tự nào và chương trình có thể được đặt vào trạng thái đã biết trước khi thực hiện kiểm thử. Một khi bạn thêm Singleton với trạng thái có thể biến đổi, điều này khó có thể thực hiện. Ngoài ra, trạng thái lâu dài của khả năng truy cập toàn cầu làm cho việc suy luận về code trở nên khó khăn hơn đặc biệt là trong môi trường đa luồng.

Môi trường đa luồng đưa ra những sự “cám dỗ” với Singleton Pattern. Các lớp bảo mật đơn giản khi truy cập không thực sự hiệu quả, do đó bảo mật hai lớp trở (DCLP) nên phổ biến. Thật không may, đây có thể là một hình thức gây thiệt hại lớn. Nó chỉ ra rằng, trong nhiều ngôn ngữ, DCLP không an toàn, ngay cả khi được thiết lập thì vẫn có khả năng vượt qua.

Việc dọn dẹp Singleton cũng có thêm một thách thức nữa:

Việc loại bỏ các Singleton không được hỗ trợ, đây có thể là một vấn đề nghiêm trọng trong một vài trường hợp. Ví dụ, trong cấu trúc của một plug-in, plug-in chỉ được tải lên an toàn sau khi tất cả đối tượng đã được loại bỏ sạch sẽ.

Không có thứ tự nào trong việc loại bỏ các Singleton khi thoát khỏi chương trình. Điều này có thể gây rắc rối với các ứng dụng có chứa các Singleton có sự phụ thuộc lẫn nhau. Khi tắt các ứng dụng như vậy, một Singleton có thể truy cập một ứng dụng khác. Một số thiếu sót có thể được khắc phục bằng cách đưa ra cơ chế bổ sung. Tuy vậy, điều này phải trả giá bằng việc sự phức tạp sẽ tăng cao trong code tránh việc thay đổi cấu trúc.

Do đó, bạn nên hạn chế sử dụng các Singleton Pattern cho các class không được khởi tạo nhiều lần. Không sử dụng việc truy cập toàn cầu của một Singleton từ code tùy ý. Thay vào đó, truy cập trực tiếp vào Singleton phải từ một vị trí xác định, từ đó có thể chuyển giao diện của nó qua một đoạn code khác. Đoạn code khác này không được sử dụng trước đó cho nên nó không phụ thuộc vào Singleton hay class nào tạo ra giao diện. Điều này phá vỡ các phụ thuộc ngăn cản việc kiểm tra và cải thiện khả năng bảo trì. Vì vậy, trong lần tới, khi bạn nghĩ về việc sử dụng hay triển khai một Singleton, hy vọng bạn sẽ tạm dừng và suy nghĩ lại.

💬 Chú thích: (*) Một design pattern mà đảm bảo rằng một class chỉ có một instance và cung cấp truy cập vào instance đó toàn cầu (**) Thực thể, giữ địa chỉ bộ nhớ