Gia Bao TSC

Singleton Design Pattern

Trong bài viết này, chúng ta sẽ cùng tìm hiểu về một Pattern tiếp theo trong nhóm Creational. Pattern này đảm bảo rằng một class chỉ có duy nhất một instance (từ này không cần phải dịch nhé) và cung cấp địa chỉ tĩnh để truy cập tới instance đó. Và chúng ta đang nhắc tới Singleton. Vậy tại sao phải sử dụng Singleton Pattern?

 Design Patterns  posted at February 26, 2019

Nhắc lại về Singleton Pattern

  • Đảm bảo rằng một class chỉ có duy nhất một instance
  • Cung cấp địa chỉ tĩnh để truy cập tới instance đó.

Tại sao cần dùng Singleton Pattern

Hầu hết các đối tượng trong một ứng dụng đều chịu trách nhiệm cho công việc của chúng và truy xuất dữ liệu tự lưu trữ (self-contained data) và các tham chiếu trong phạm vi được đưa ra của chúng. Tuy nhiên, có nhiều đối tượng có thêm những nhiệm vụ và ảnh hưởng rộng hơn, chẳng hạn như quản lý các nguồn tài nguyên bị giới hạn hoặc theo dõi toàn bộ hoạt động của hệ thống. Ví dụ có thể tồn tại rất nhiều máy in (Printer) trong hệ thống những chỉ có thể tồn tại duy nhất một Printer Spooler.

Làm thế nào để implement Singleton Pattern

Chúng ta cần trả lời 2 câu hỏi bên dưới:

  • Làm sao để một class chỉ có thể có duy nhất một instance?

Class cần phải có Private constructor để đảm bảo rằng class khác không thể truy cập vào constructor và tạo ra instance mới.

Cần phải có một biến private static là instance của class đó để đảm bảo rằng nó là duy nhất và chỉ được tạo ra trong class đó.

  • Làm sao có thể cung cấp một cách toàn cầu để truy cập tới instance đó?

Tạo ra một public static method để trả về instance vừa khởi tạo bên trên và đây là cách duy nhất các class khác có thể truy cập vào instance của class này.

Có những cách nào để implement Singleton Pattern

1. Eager initialization


public class EagerInitializedSingleton {

   private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

   //private constructor to avoid client applications to use constructor
   private EagerInitializedSingleton(){}

   public static EagerInitializedSingleton getInstance(){
       return instance;
   }
}

Đây là cách dễ nhất nhưng nó có một nhược điểm là mặc dù instance đã được khởi tạo nhưng có thể sẽ không dùng tới. Vì vậy chúng ta có cách thứ 2.

2. Lazy initialization


public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton(){}

    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

Cách này rõ ràng là đã khắc phục được nhược điểm của cách 1 (Eager initialization). Chỉ khi nào getInstance được gọi thì instance mới được khởi tạo. Tuy nhiên cách này chỉ sử dụng tốt trong trường hợp đơn luồng (single threading), trường hợp nếu có từ 2 luồng cùng chạy (multi threading) và cùng gọi hàm getInstance tại cùng một thời điểm thì đương nhiên là chúng ta có ít nhất 2 instance. Vậy phải làm sao với trường hợp đa luồng (multi threading). Chúng ta xem cách tiếp theo.

3. Thread Safe initialization


public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
}

Cách đơn giản nhất là chúng ta gọi phương thức synchronized của hàm getInstance và như vậy hệ thống đảm bảo rằng tại một thời điểm chỉ có thể có 1 luồng được phép truy cập vào hàm getInstance, và cũng qua đó đảm bảo rằng tại một thời điểm chỉ có thể có 1 instance được tạo ra. Tuy nhiên một method synchronized sẽ chạy rất chậm và tốn hiệu năng. Vì vậy chúng ta cũng cần phải cải tiến đi một chút.

4. Thread Safe Upgrade initialization

Thay vì chúng ta Thread Safe cả method getInstance thì chỉ thực hiện với một đoạn mã quan trọng


public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

    public static ThreadSafeSingleton getInstance(){
        if(instance == null){
            synchronized(ThreadSafeSingleton.class){
               instance = new ThreadSafeSingleton();
            }
        }
        return instance;
    }
}

Có rất nhiều cách implement Singleton Pattern. Với ứng dụng single threading thì có thể sử dụng cách 2, còn đối với những ứng dụng multi threading thì nên chọn cách 4.

Cài đặt cho cách số 2 (Lazy initialization)

Liên quan tới các pattern khác

  • Abstract Factory: thường là Singleton để trả về các đối tượng factory duy nhất.

  • Builder: dùng để xây dựng một đối tượng phức tạp, trong đó Singleton được dùng để tạo một đối tượng truy cập tổng quát (Director).

  • Prototype: dùng để sao chép một đối tượng, hoặc tạo ra một đối tượng khác từ Prototype của nó, trong đó Singleton được dùng để chắc chắn chỉ có một Prototype.

Java API

  • Lớp java.lang.Runtime là lớp Singleton, để lấy được đối tượng duy nhất của nó, ta gọi phương thức getRuntime(). Tương tự, lớp java.awt.Desktop cũng là lớp Singleton, tạo đối tượng duy nhất bằng phương thức getDesktop(). Singleton không phổ biến như ta nghỉ, nó chỉ áp dụng với lớp cần bảo đảm chỉ có một thể hiện duy nhất.

Các trường hợp nên dùng

  • Đảm bảo rằng chỉ có một thể hiện của lớp.
  • Quản lý việc truy cập tốt hơn vì chỉ có một thể hiện duy nhất.
  • Quản lý số lượng thể hiện của một lớp. Trường hợp này không nhất thiết chỉ có một thể hiện, bạn cần kiểm soát số lượng thể hiện trong giớn hạn chỉ định.

Source Code

Gia Bao TSC