Chào mừng bạn đến với series Design Pattern trong lập trình hướng đối tượng
Chào mừng anh em trở lại với series Design Pattern thực chiến trong lập trình hướng đối tượng. Singleton là một trong những design pattern quan trọng nhất thuộc nhóm Creational Pattern. Mục tiêu chính của nó là đảm bảo rằng một class chỉ có duy nhất một instance trong toàn bộ vòng đời của chương trình và cung cấp một điểm truy cập toàn cục đến instance này. Điều này rất hữu ích trong nhiều tình huống đòi hỏi sự nhất quán về trạng thái của đối tượng hoặc tài nguyên, chẳng hạn như khi làm việc với database connection hay loggers. Trong bài viết này, chúng ta sẽ cùng tìm hiểu khái niệm, cấu trúc và cách triển khai Singleton trong ngôn ngữ lập trình Java.
I. Khái niệm và cấu trúc
1. Ý định (Intent)
Trích từ cuốn Design patterns Elements of Reusable Object-Oriented Software:
Đảm bảo một class chỉ có một instance, và cung cấp một điểm truy cập toàn cục đến nó.
GoF
Nhóm: Creational pattern
Pattern này có hai mục tiêu chính:
- Đảm bảo một class chỉ có một instance duy nhất, ngăn chặn việc khởi tạo nhiều instance của một class.
- Cung cấp một điểm truy cập toàn cục (global access point): sử dụng một static method cho phép các thành phần khác truy cập vào instance duy nhất này.
2. Cấu trúc tổng quát
Cấu trúc của Singleton rất đơn giản, chỉ có một class:
Singleton: class chứa ít nhất ba thành phần chính:
- Private static instance
private static Instance
- Private constructor
private Singleton()
- Public static method
getInstance()
cung cấp điểm truy cập tới instance cho các thành phần khác.
3. Ứng dụng
Singleton được sử dụng khi cần quản lý tài nguyên hệ thống một cách nhất quán. Một số ví dụ thực tế có thể bao gồm quản lý database connection, loggers hoặc các cấu hình hệ thống phức tạp nhưng đòi hỏi sự nhất quán trên toàn bộ hệ thống.
II. Thực hành triển khai trong Java
1. Naive implementation
Đúng như định nghĩa trên, chúng ta có:
- Private static instance
- Private constructor
- Public static method
getInstance()
NaiveSingleton
java
public class NaiveSingleton {
private static NaiveSingleton instance;
private NaiveSingleton() {}
public static NaiveSingleton getInstance() {
if(Objects.isNull(instance)) {
instance = new NaiveSingleton();
}
return instance;
}
}
2. Triển khai an toàn với đa luồng (Thread-safe implementation)
Vấn đề của implementation đầu tiên là nó sẽ gây ra lỗi khi có nhiều thread cùng tham gia vào quá trình tạo instance. Hãy tưởng tượng trường hợp có hai thread cùng gọi phương thức getInstance()
khi instance chưa được khởi tạo. Lúc này, cả hai sẽ cùng tạo ra hai instance, làm phá vỡ quy tắc chỉ cho phép một instance duy nhất.
Để giải quyết vấn đề này, có những cách sau:
- Static Initialization: Khởi tạo instance ngay tại câu lệnh định nghĩa instance. Điểm yếu của phương pháp này là nó không hỗ trợ lazy load.
java
private static Singleton instance = new Singleton();
- Synchronized Method: Phương pháp này gặp vấn đề về performance vì các thread phải chờ khóa được giải phóng mỗi khi gọi phương thức
getInstance()
.
java
public static synchronized Singleton getInstance() {
if(Objects.isNull(instance)) {
instance = new Singleton();
}
return instance;
}
- Double-Checked Locking: Phương pháp này vừa hỗ trợ lazy load và cũng cải thiện đáng kể hiệu suất.
java
public class ThreadSafeSingleton {
private static volatile ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
public static ThreadSafeSingleton getInstance() {
ThreadSafeSingleton result = instance;
if(Objects.nonNull(result)) {
return result;
}
synchronized (ThreadSafeSingleton.class) {
if (Objects.isNull(instance)) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}
}
Để tìm hiểu đầy đủ, bạn có thể tham khảo mã nguồn tại link: design-pattern-made-easy/singleton.
III. Lời kết
Singleton là một pattern đơn giản nhưng mạnh mẽ, giúp tối ưu hóa việc quản lý tài nguyên và đảm bảo tính nhất quán trong hệ thống. Từ cách tiếp cận naive đến các giải pháp tối ưu như Thread-safe Singleton và Double-checked locking, chúng ta có thể lựa chọn phương pháp phù hợp với yêu cầu cụ thể của từng ứng dụng. Sử dụng Singleton không chỉ cải thiện hiệu suất mà còn giúp duy trì một kiến trúc rõ ràng và hiệu quả. Hẹn gặp lại bạn trong các bài viết tiếp theo!
IV. Tài liệu tham khảo
- Refactoring.guru
- Head First Design Pattern - O'Reilly
- Design Patterns: Elements of Reusable Object-Oriented Software - GoF
Nếu bạn thấy bài viết hữu ích, hãy click upvote để ủng hộ bài viết này. Nếu bạn có bất kỳ câu hỏi hay ý kiến nào, hãy để lại comment để chúng ta cùng trao đổi nhé! Bạn cũng có thể tham khảo các bài viết khác của tôi tại blog cá nhân hoặc kết nối với tôi qua LinkedIn.
source: viblo