Get in touch
or send us a question?
CONTACT

Vài điều về Singleton có thể bạn đã biết ::))

Singleton là gì?

Design Patterns, những giải pháp tổng quát nhất có thể tái sử dụng cho các trường hợp khi cần thiết kế kiến trúc phần mềm. Singleton là một trong số đó, thuộc nhóm khởi tạo — Creational Patterns.
Singleton đối ứng với các cơ chế tạo và kiểm soát việc tạo đối tượng. Đúng như cái tên “đơn độc” của mình, Singleton đảm bảo cho mỗi class chỉ có duy nhất một thể hiện được khởi tạo và được truy xuất mọi nơi.
Đối với ví dụ trên, Singleton sẽ chỉ tạo ra một thể hiện của đối tượng — instance — khởi tạo duy nhất một lần nhưng có khả năng truy xuất và sử dụng mọi nơi.
Trực quan hơn ta có một biểu đồ UML thể hiện Singleton:

Singleton UML Diagram

Vậy Singleton có gì?

Triển khai Singleton có khá nhiều cách tuy nhiên cách nào cũng cần đảm bảo những yếu tố sau mới đảm bảo class đó là class Singleton:
+ Private static field — một trường static chỉ có khả năng truy cập trong class để khởi tạo instance, đảm bảo cho tính duy nhất và chỉ được tạo trong class đó của instance.
+ Private constructor — một phương thức khởi tạo cũng hạn chế khả năng truy cập từ bên ngoài, ngăn việc tạo instance từ bên ngoài.
+ Public method — một phương thức trả về instance không hạn chế khả năng truy cập từ đó có thể truy xuất và sử dụng instance mọi nơi.
Triển khai Singleton thế nào?
Như đã nói ở trên, có rất nhiều cách để triển khai Singleton nhưng trong bài viết này, ta sẽ đề cập đến những cách thức triển khai đơn giản, chung nhất cho Singleton.

a. Eager Initialization:
Kiểu khởi tạo “xông xáo”, nghĩa là không cần biết em là ai, không cần biết em từ đâu, nhưng vẫn quyết tâm khởi tạo ra một instance ngay khi class được load lần đầu.
Đây là cách khởi tạo đơn giản nhất, nhanh đối với instance không quá tốn nhiều tài nguyên. Nên từ đó phát sinh những hạn chế như sau:
+ Instance tạo ra có thể không cần dùng đến.
+ Performance thấp, tốn tài nguyên do khởi tạo instance ngay khi class được load.
+Không có cách nào để bắt lỗi và xử lý lỗi (exception) trong quá trình khởi tạo.
Ví dụ về cách khởi tạo “xông xáo”:

b. Static block Initialization:
Tương tự như Eager Init nhưng cách triển khai này có thêm một static block cung cấp xử lý ngoại lệ trong quá trình khởi tạo. Dù khắc phục được nhược điểm về bắt và xử lý lỗi của Eager nhưng Static Block vẫn còn hạn chế về performance cũng như tính hữu dụng của instance.

c. Lazy Initialization
Đây là cách triển khai mở rộng, giải quyết hạn chế cho hai cách triển khai trên nhưng chỉ hoạt động tốt với Thread đơn lẻ.
Kiểu khởi tạo “lười” này chỉ tạo instance khi cần dùng đến. Nghĩa là việc khởi tạo và trả về instance sẽ cùng thực hiện trong method được cung cấp khi method đó được gọi.
Tránh được hạn chế về xử lý ngoại lệ, cải thiện được performance và tối ưu instance được tạo thì Lazy lại gặp lỗi với đa luồng (multi thread) do method có thể khởi tạo cùng lúc nhiều đối tượng ở các luồng khác nhau, phá vỡ tính chất của Singleton.

d. Thread Safe Singleton:
Thread safe — luồng an toàn — sinh ra như một cách giải quyết vấn đề cho Lazy, Thread Safe làm việc với đa luồng. Thread Safe triển khai tương tự Lazy nhưng trong phương thức truy cập được thêm vào từ khóa synchronized, đồng bộ hóa quá trình truy cập và khởi tạo, tạo ra sự tuần tự, luồng nào đến trước sẽ được xử lý trước.
Tuy nhiên thread safe có performance thấp do synchronized bao quát tất cả các quá trình có trong phương thức nên làm chậm quá trình truy xuất instance.

Để tối ưu hiệu suất (Performance):

e. Bill Pugh Implementation:
Phương pháp này do Bill Pugh triển khai dựa trên cơ chế static nested class. Ông tạo ra một lớp Helper private ngay trong class Singleton, tăng tính bao gói dữ liệu giữa các lớp, thuận tiện hơn cho việc đọc và bảo trì code. Khi Singleton được tải vào bộ nhớ thì SingletonHelper chưa được tải vào. Khi và chỉ khi phương thức trả về được gọi, SingletonHelper mới được tải và tạo ra instance.
Cách làm của Bill Pugh tránh được lỗi cơ chế khởi tạo instance của Singleton trong Multi Thread, performance cao do tách biệt được quá trình xử lý. Cách làm này được đánh giá là cách triển khai Singleton nhanh và hiệu quả nhất.

Ngoài ra còn có các cách triển khai nâng cao với Enum Singleton, Serializable hoặc phá vỡ cấu trúc Singleton với Reflection API nhưng ta sẽ không triển khai chi tiết trong bài viết này.

Lời kết

Singleton trên thực tế thường dùng để thiết kế hay tạo ra đối tượng implement cho các class Logger, Cache, Connect Pool, Thread Pool,… — là những đối tượng chỉ cần tạo ra một lần, không cần thiết tạo thêm các thể hiện khác.

Singleton có quan hệ tốt với các Patterns khác, góp phần xây dựng pattern như: Abstract Factory, Builder, Facade, Prototype,…

Với sự “đơn thương độc mã” quyền lực của mình, Singleton là một mẫu thiết kế quan trọng trong thiết kế cấu trúc phần mềm, phát huy tốt cho những tình huống cần quản lý khởi tạo và truy cập của đối tượng.

Nguồn: https://medium.com/@minhlee.fre.mta/v%C3%A0i-%C4%91i%E1%BB%81u-v%E1%BB%81-singleton-5d3bd1e1022a