Cách vận dụng Singleton pattern p1: Singleton cổ điển
Các singleton pattern nghe co vẻ đơn giản, nhưng để sử dụng nó trong quá trình phát triển thì không đơn giản chút nào. Singleton pattern ứng dụng trong các chương trình đa luồng, classloader, serialization. Do đó, trong loạt bài viết về Singleton pattern. Mình sẽ trình bày về cách vận dụng nó từ ...
Các singleton pattern nghe co vẻ đơn giản, nhưng để sử dụng nó trong quá trình phát triển thì không đơn giản chút nào. Singleton pattern ứng dụng trong các chương trình đa luồng, classloader, serialization.
Do đó, trong loạt bài viết về Singleton pattern. Mình sẽ trình bày về cách vận dụng nó từ những trương trình đơn giản đến phức tạp.
Đầu tiên mình sẽ giới thiệu qua về Singleton pattern đơn giản nhất mà nhiều người biết đến.
Như bạn có thể thấy, Singleton duy trì một tham chiếu tĩnh đến duy nhất singleton và trả về một tham chiếu đến, từ một phương thức tĩnh instance().
Ví dụ 1 thể hiện một cách cài đặt Singleton pattern cổ điển
public class ClassicSingleton { private static ClassicSingleton instance = null; protected ClassicSingleton() { // Exists only to defeat instantiation. } public static ClassicSingleton getInstance() { if(instance == null) { instance = new ClassicSingleton(); } return instance; } }
Singleton cài đặt ở ví dụ 1 rất dễ hiểu. Lớp ClassicSIngleton duy trì một tham chiếu tĩnh đến thể hiện singleton và trả về cái tham chiếu từ hàm getInstance().
Có một số điểm hay của lớp ClassicSingleton. Đầu tiên, ClassicSingleton sử dụng kĩ thuật gọi là lazy instantiation để tạo singleton; kết quả là thể hiện singleton không được tạo ra đến khi phương thức getInstance() được gọi cho lần đầu tiên. Kỹ thuật đó đảm bảo rằng thể hiện singleton được tạo chỉ khi cần.
Thứ hai, nhận thấy rằng ClassicSingleton cài đặt một prorected constructor vì vậy client không thể khởi tạo thể hiện ClassicSingleton; tuy nhiên, bạn có thể ngạc nhiên khi phát hiện ra rằng đoạn code sau hoàn toàn hợp lệ:
public class SingletonInstantiator { public SingletonInstantiator() { ClassicSingleton instance = ClassicSingleton.getInstance(); ClassicSingleton anotherInstance = new ClassicSingleton(); ... } }
Làm thế nào mà ClassicSingleton tạo được thể hiện, nếu constructor của ClassicSingleton là protected ? Câu trả lời là constructor protected có thể được gọi bời lớp con và lớp khác trong cùng một gói. Để khắc phục điều này chúng ta có hai giải pháp: bạn có thể thiết lập constructor của ClassicSingleton là private; tuy nhiên, điều đó nghĩa là ClassicSingleton không thể có lớp con. Thỉnh thoảng, đó là giải pháp tốt giúp trình biên dịch tối ưu hóa hiệu năng. Các giải pháp khác là đặt lớp Singleton của bạn trong một gói tường minh, do đó các lớp khác package không thể khởi tạo thể hiện.
Một điểm thú vị thứ ba về ClassicSingleton: nó có thể có nhiều thể hiện singleton nếu các lớp khác nạp bởi các classloader khác truy cập một singleton. Ví dụ, một số container servlet sử dụng classloader riêng biệt cho mỗi servlet, vì vậy nếu hai servlets truy cập vào một singleton, họ đều sẽ có thể hiện của riêng họ.
Thứ tư, nếu ClassicSingleton cài đặt java.io.Serializable, thì thể hiện của các lớp này có thể serialized và deserialized. Tuy nhiên, nếu bạn serialize một đối tượng singleton và rồi deserialize đối tượng đó nhiều hơn một lần, thì bạn sẽ có nhiều hơn một thể hiện của singleton đó.
Cuối cùng, và có lẽ quan trọng nhất, lớp ClassicSingleton không thread-safe. Nếu hai luồng, chúng ta gọi là luồng 1 và luồng 2 cùng gọi ClassicSingleton.getInstance() cùng một thời điểm.
Như bạn có thể thấy, mặc dù Singleton pattern là một trong những mẫu thiết kế đơn giản. Nhưng có rất nhiều vấn đề với chúng. Trong những phần sau, mình sẽ tiếp tục chủ đề vận dụng Singleton trong các trường hợp cụ thể như trong môi trường đa luồng, classloader, serializable, ...