0

Android Design Patterns: The Singleton Pattern

Singleton Pattern là gì ?

Singleton Pattern là một design pattern đảm bảo rằng một class chỉ có một instance và một điểm truy cập toàn cục do class đó cung cấp. Bất cứ lúc nào nhiều class hay nhiều đối tượng yêu cầu lớp đó, chúng sẽ nhận được cùng một instance của nó.

Lấy luôn ví dụ về điện thoại di động và chủ sở hữu của nó. Điện thoại thường được sở hữu bởi một người, trong khi một người có thể sở hữu nhiều điện thoại. Bất cứ khi nào một trong những điện thoại này đổ chuông, cùng 1 chủ sở hữu sẽ nhấc máy.

Lợi ích của Singleton Pattern

Trong một ứng dụng Android điển hình, có rất nhiều đối tượng mà chúng ta chỉ cần một instance toàn cục của nó, cho dù bạn đang sử dụng nó trực tiếp hay đơn giản là truyền nó sang một class khác.

Ví dụ : bộ nhớ caches, OkHttpClient, HttpLoggingInterceptor, Retrofit, Gson, SharedPreferences, các đối tượng trả về... Nếu chúng được khởi tạo nhiều lần, sẽ xảy ra các vấn đề như lạm dụng tài nguyên, nhầm lẫn các kết quả.

Implementation

Rất dễ dàng để implement pattern này. Đoạn code sau sẽ chỉ ra cách một Singleton được tạo ra :

public class Singleton  {
 
    private static Singleton INSTANCE = null;
 
    // other instance variables can be here
     
    private Singleton() {};
 
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return(INSTANCE);
    }
     
    // other instance methods can follow 
}

Trong đoạn code trên, chúng ta có một biến static INSTANCE để lưu trữ một instance của class. Chúng ta cũng viết hàm khởi tạo private bởi vì chúng ta muốn class chỉ có thể khởi tạo chính nó. Phương thức getInstance() đảm bảo rằng lớp này đã được khởi tạo

Ví dụ : Tạo Single Instance của Retrofit

Retrofit là một thư viện phổ biến để kết nối một dịch vụ webservice REST bằng cách dịch các API thành các interface Java.

Trong một ứng dụng Android, bạn sẽ cần một instance toàn cục của đối tượng Retrofit để các thành phần khác của ứng dụng chẳng hạn như UserProfileActivity hoặc SettingsActivity, có thể sử dụng nó để thực hiện một kết nối network, mà không cần tạo một instance mỗi lần chúng cần nó.

Việc tạo nhiều instance sẽ gây ra tình trạng thừa thãi, tốn tài nguyên, chiếm bộ nhớ không cần thiết trên thiết bị.

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
  
public class RetrofitClient {
  
    private static Retrofit retrofit = null;
  
    public static Retrofit getClient(String baseUrl) {
        if (retrofit==null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }

Cho nên, bất cứ lúc nào client A gọi RetrofitClient.getClient(), nó sẽ tạo ra một instance nếu nó chưa được tạo, và rồi khi client B gọi phương thức này, nó kiểm tra nếu instance của Retrofit đã tồn tại hay chưa. Nếu đã được tạo rồi, nó trả về instance cho client B thay vì tạo ra một instance mới.

Làm việc với Multithreading

Trong một ứng dụng Android, bạn có thể sử dụng nhiều thread để thực hiện nhiều tác vụ khác nhau. Những thread này có thể thực hiện đồng thời. Trong trường hợp của Singleton nêu trên, điều này có thể dẫn đến việc tạo ra nhiều đối tượng instance, đi ngược lại với mong muốn là sử dụng một instance duy nhất.

Do đó, phương thức getInstance() nêu trên chưa phải là an toàn ( thread-safe). Chúng ta sẽ xử lí như nào đây 😄

Đồng bộ hóa getInstance()

Một trong những cách để làm cho đoạn code getInstance() đúng như mong muốn là đồng bộ hóa nó. Tức là chỉ cho phép một thread chạy một phương thức tại cùng một thời điểm, buộc các thread còn lại phải chờ đợi hoặc rơi vào trạng thái block.

public class Singleton  {
 
    private static Singleton INSTANCE = null;
 
    // other instance variables can be here
     
    private Singleton() {};
 
    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return(INSTANCE);
    }
     
    // other instance methods can follow 
}

Cách xử lí này làm cho thread của chúng ta thực hiện đúng như mong muốn là tạo ra một instance duy nhất, nhưng nó lại là một cách xử lí chưa tối ưu. Nói cách khác, việc xử lí này có thể làm giảm hiệu suất. Vì vậy, bạn phải điều tra và xem liệu hiệu suất có bị ảnh hưởng trong ứng dụng của bạn.

Tạo nhanh một Instance

Cách tiếp cận khác để làm việc với đa luồng ( multithread ) truy cập singleton là tạo ra instance ngay khi lớp được load hoặc khởi tạo ( bởi Android ClassLoader trong Dalvik VM ). Điều này làm cho thread chạy an toàn. Sau đó, instance sẽ sẵn sàng trước khi bất kì thread nào truy cập vào biến INSTANCE :

public class Singleton  {
 
    private static Singleton INSTANCE = new Singleton();
 
    // other instance variables can be here
     
    private Singleton() {};
 
    public static Singleton getInstance() {
       return(INSTANCE);
    }
     
    // other instance methods can follow 
}

Một hạn chế đối với cách xử lí này là bạn có thể tạo ra một instance mà không bao giờ được sử dụng đến, do đó chiếm bộ nhớ không cần thiết. Vì vậy cách tiếp cận này thường chỉ được sử dụng nếu bạn chắc chắn rằng singleton sẽ được truy cập.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.