Singleton pattern là gì? 9 cách để implement singleton pattern phổ biến nhất

Admin 01 Nov 2021 276

Trong nền công nghiệp phần mềm, mô hình Singleton là một mẫu thiết kế phần mềm nhằm hạn chế sự khởi tạo của lớp (class) đối tượng. Điều này vô cùng hữu ích khi cần một đối tượng chính xác để điều phối hành động trên toàn hệ thống. Khái niệm đôi khi được khái quát hóa cho các hệ thống vận hành hiệu quả hơn khi chỉ có một đối tượng tồn tại hoặc hạn chế sự khởi tạo cho một số lượng đối tượng nhất định. Thuật ngữ này được xuất phát từ khái niệm toán học của một Singleton.Vậy Singleton pattern là gì? Singleton pattern dùng để làm gì? Có những cách nào để implement singleton pattern? Singleton pattern được ứng dụng ở đâu? Chúng ta cùng tìm hiểu qua bài viết sau nhé!



                                              

Singleton pattern là gì?

Singleton pattern là gì?

Singleton pattern là 1 trong 5 design pattern thuộc nhóm Creational Design Pattern.Singleton pattern đảm bảo chỉ duy nhất một thể hiện (instance) được tạo ra và bạn có thể truy xuất được thể hiện duy nhất đó mọi lúc mọi nơi trong chương trình.


                          Singleton có 1 instance duy nhất và được các class khác gọi tới                              

Singleton pattern dùng để làm gì?

Việc sử dụng Singleton pattern sẽ giúp chúng ta đảm bảo những điều sau đây:

  • Đảm bảo chỉ có 1 thể hiện (instance) của lớp (class).

  • Giúp quản lý việc truy cập tốt hơn vì chỉ có 1 thể hiện (instance) duy nhất.

  • Quản lý được số lượng thể hiện (instance) của một lớp (class)  trong một giới hạn chỉ định.

Các nguyên tắc cơ bản để implement Singleton pattern

Chúng ta có rất nhiều cách để implement Singleton pattern. Nhưng dù cho việc implement bằng cách nào đi nữa cũng cần dựa vào những nguyên tắc dưới cơ bản dưới đây:

  • Dữ liệu instance (private và static) là đối tượng duy nhất của lớp Singleton.

  • Private hoặc protected constructor nhằm hạn chế người dùng tạo instance trực tiếp từ lớp (class) bên ngoài.

  • Đặt private static final variable để đảm bảo biến chỉ được khởi tạo trong class.

  • Có một method public static để return instance đã được khởi tạo ở trên.

9 cách để implement Singleton pattern phổ biến nhất

Dựa trên những nguyên tắc thiết kế Singleton trên, chúng ta có 9 cách implement Singleton pattern sau:

Eager initialization (Khởi tạo sớm)


Singleton Class sẽ được khởi tạo ngay khi chương trình chạy. Đây là cách dễ dàng nhất, đơn giản nhất nhưng nó có một nhược điểm: mặc dù instance đã được khởi tạo nhưng có thể sẽ không dùng tới.


Ví dụ:


                Eager initialization dễ cài đặt nhưng nó dễ dàng bị phá vỡ bởi Reflection

Static block initialization

Chúng ta  làm tương tự như Eager initialization chỉ khác ở phần static block cung cấp thêm lựa chọn cho việc handle exception hay các xử lý khác.


Ví dụ:

Lazy Initialization 


Lazy Initialization ra đời đã giúp khắc phục nhược điểm của Eager Initialization. Chỉ khi nào được gọi, instance mới được khởi tạo. Nó giúp bạn không phải khởi tạo class khi bạn không cần. Tuy nhiên, đối với thao tác create instance quá chậm thì người dùng có thể phải chờ lâu cho lần sử dụng đầu tiên.


Lazy Initialization cũng chỉ hoạt động tốt với trường hợp chương trình của chúng ta là đơn luồng (single-thread). Nếu tại một thời điểm mà có hai luồng (multi-thread) cùng gọi đến phương thức getInstance() thì sẽ xảy ra trường hợp 2 thực thể (instance) sẽ được khởi tạo cùng một lúc.Để khắc phục nhược điểm trên, phương pháp Thread Safe Singleton đã được ra đời.

Thread Safe Initialization


Thread Safe Initialization khắc phục nhược điểm của Lazy Initialization bằng cách gọi phương thức synchronized của hàm getInstance(). Như vậy sẽ đảm bảo cho việc tại một thời điểm chỉ có một luồng (single-thread) được phép truy cập vào hàm getInstance() và chỉ có duy nhất 1 instance của class. 


Tuy nhiên, phương pháp này sẽ làm chương trình chạy chậm và tốn hiệu năng. Bất kỳ một Thread nào gọi đến đều phải chờ nếu có một Thread khác đang sử dụng. Có những tác vụ xử lý trước và sau khi tạo instance không cần thiết phải block. Do vậy chúng ta cần có một phương pháp ưu việt hơn, đó chính là Double Check Locking Singleton.


Ví dụ của Thread Safe Initialization

Double Check Locking Singleton

Để implement Singleton pattern theo cách này, chúng ta sẽ kiểm tra sự tồn tại instance của class, với sự hỗ trợ của đồng bộ hóa, hai lần trước khi khởi tạo. Bạn cần khai báo volatile cho instance nhằm tránh class làm việc không chính xác do quá trình tối ưu hóa của trình biên dịch


          Ví dụ về Double Check Locking Singleton

Bill Pugh Singleton Implementation


Bill Pugh Singleton implementation tạo ra static nested class với chức năng 1 Helper khi muốn tách biệt chức năng cho 1 class function rõ ràng hơn. Đây là cách thường được sử dụng nhiều và có hiệu suất tốt.


                                    Ví dụ về Bill Pugh Singleton Implementation


Trong khi Singleton được tải vào bộ nhớ thì Singleton Helper vẫn chưa được tải vào. Singleton Helper chỉ được tải vào khi phương thức getInstance() được gọi. Phương pháp này giúp tránh được lỗi cơ chế khởi tạo thực thể của Singleton trong Multi-Thread, performance cao. Do đã tách biệt được quá trình xử lý. Cũng chính vì vậy mà phương pháp này được người lập trình đánh giá là cách triển khai Singleton nhanh và hiệu quả nhất.

Reflection Singleton Pattern


Reflection có thể được dùng để phá vỡ Pattern của Eager Initialization và Bill Pugh Singleton như ví dụ dưới đây:


Ví dụ về Reflection Singleton Pattern


Khi đó Output của chương trình là: 


Enum Singleton


Khi dùng Enum các params chỉ được khởi tạo 1 lần duy nhất. Đây cũng là cách để bạn tạo ra Singleton instance.


Ví dụ:



Lưu ý 2 điều sau đây khi dùng Enum Singleton:

  • Enum có thể sử dụng như một Singleton. Nhược điểm của nó là không thể extends từ một class  được, nên khi sử dụng cần xem xét vấn đề này.

  • Hàm constructor của enum là có tính chất lazy, nghĩa là khi được sử dụng mới chạy hàm khởi tạo và nó chỉ chạy duy nhất một lần. Nếu bạn muốn sử dụng như một Eager Singleton thì cần gọi stance trong một static block khi bắt đầu chương trình.

Serialization and Singleton


Trong các hệ thống phân tán (distributed system), đôi lúc chúng ta cần implement interface Serializable trong lớp Singleton để chúng ta có thể lưu trữ trạng thái của nó trong file hệ thống và truy xuất lại nó sau.


Ví dụ:



Đoạn code test quá trình Serialize/ Deserialize:



Output của chương trình là: 



Như ở ví dụ trên, Deserialize đối tượng của Serialized Singleton khác với đối tượng gốc.

Nhưng nó sẽ không xảy ra khi bạn dùng phương pháp  Enum.


Trên thực tế, vẫn có cách khắc phục khi sử dụng class Serialized Singleton là implement một phương thức readResolve(). Nhưng khi chúng ta thật sự gặp vấn đề và cần sử dụng Serialize/ Deserialize, thì nên sử dụng Enum sẽ đơn giản hơn.

Một số ứng dụng của Singleton Pattern

Chúng ta có thể thấy Singleton Pattern được ứng dụng trong các trường hợp: 


  • Trường hợp giải quyết các bài toán cần truy cập vào các ứng dụng như: truy cập giao diện phần cứng, tệp cấu hình, Shared resource, Logger, Configuration, Caching, Thread pool ,...

    Cụ thể:
    Truy cập giao diện phần cứng: Singleton được sử dụng tùy thuộc vào yêu cầu. Các lớp Singleton cũng được sử dụng để ngăn chặn truy cập đồng thời của class. Trên thực tế, Singleton có thể được sử dụng trong trường hợp yêu cầu giới hạn sử dụng tài nguyên phần cứng bên ngoài. Chẳng hạn như: máy in phần cứng nơi mà  bộ đệm in có thể được tạo thành một Singleton nhằm tránh nhiều truy cập đồng thời và tạo ra tắc nghẽn.


Logger: Các class Singleton được ứng dụng trong các tệp nhật ký. Các tệp nhật ký được tạo bởi đối tượng class trình ghi nhật ký. Ví dụ, một ứng dụng trong đó tiện ích ghi nhật ký phải tạo một tệp nhật ký dựa trên các thông báo nhận được từ người dùng. Nếu có nhiều ứng dụng khách sử dụng lớp tiện ích ghi nhật ký này, chúng có thể sẽ tạo nhiều bản sao của lớp này và có thể gây ra lỗi trong quá trình truy cập cùng lúc vào cùng một file trình ghi nhật ký. Chúng ta có thể dùng lớp tiện ích ghi nhật ký như một lớp đơn và cung cấp một điểm tham chiếu chung để mỗi người dùng có thể sử dụng tiện ích này và không có 2 người dùng nào truy cập nó cùng một lúc.


Tệp cấu hình: Singleton pattern rất phù hợp để ứng dụng vào tệp cấu hình vì nó ngăn cản nhiều người dùng truy cập liên tục và đọc tệp cấu hình hoặc tệp thuộc tính.

  • Một số design pattern sử dụng Singleton để thiết kế: Abstract Factory, Builder, Prototype, Facade,…

  • Sử dụng trong: java.lang.Runtime, java.awt.Desktop,..

Trên đây là một số những ứng dụng phổ biến của Singleton pattern. Ngoài ra, Singleton pattern còn có rất nhiều ứng dụng khác.


Qua bài viết trên, chúng tôi hy vọng đã giúp bạn hiểu hơn về Singleton pattern là gì? Chức năng, các cách thực thi và ứng dụng của Singleton pattern trong thực tế. Từ đó bạn có thể áp dụng Singleton một cách hiệu quả nhất trong công việc của mình nhé!

Admin

Nguồn: Singleton pattern là gì? 9 cách để implement singleton pattern phổ biến nhất

Bài viết liên quan

NEWSLETTER

Nhập địa chỉ email của bạn dưới đây để đăng ký nhận tin mới nhất

KẾT NỐI VÀ THEO DÕI