Design pattern singleton là gì

Singleton Pattern là một pattern cực kỳ cơ bản và hiệu quả mà chắc chắn rất nhiều lập trình viên trong chúng ta vẫn sử dụng thường xuyên nên mình sẽ chỉ nói qua về khái niệm thôi.

Mục đích của Singleton Pattern

Một class thuộc dạng Singleton có nghĩa là: nó chỉ có một instance duy nhất, bất kỳ ở đâu đều có thể truy cập tới instance của class singleton đó.
Điển hình nhất mà ta vẫn thấy khi lập trình game đó là GameManager, AudioManager, RoundManager, GameCenterManager, AdsManager,…

Design pattern singleton là gì
                                              

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.


Design pattern singleton là gì

                          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ụ:


Design pattern singleton là gì

                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ụ:

Design pattern singleton là gì

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.


Design pattern singleton là gì

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


Design pattern singleton là gì

          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.


Design pattern singleton là gì

                                    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:


Design pattern singleton là gì

Ví dụ về Reflection Singleton Pattern


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


Design pattern singleton là gì

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ụ:


Design pattern singleton là gì


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ụ:


Design pattern singleton là gì


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


Design pattern singleton là gì


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


Design pattern singleton là gì


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é!

Mẫu thiết kế Singleton dùng để làm gì?

Singleton Pattern một mẫu thiết kế (design pattern) được sử dụng để bảo đảm rằng mỗi một lớp (class) chỉ có được một thể hiện (instance) duy nhất và mọi tương tác đều thông qua thể hiện này.

Sử dụng Singleton pattern khi nào?

Sử dụng Singleton Pattern khi nào?.
Vì class dùng Singleton chỉ tồn tại 1 Instance nên nó thường được dùng cho các trường hợp giải quyết những bài toán cần truy cập vào các ứng dụng như: Shared resource, Logger, Configuration, Thread pool,...
Sử dụng trong một số class của core java như: java. lang. Runtime, java. awt..

Tại sao nên dùng Singleton?

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.

Singleton sét là gì?

Trong toán học, 1 singleton còn được gọi một tập đơn vị, một tập hợp có đúng một phần tử. Ví dụ: tập {null} 1 singleton. Design Pattern này được dùng khi ta muốn đảm bảo chỉ có duy nhất một object được sinh ra trong toàn hệ thống. Tức chúng ta giới hạn việc khởi tạo một class đối với một object.