Một lần và mãi mãi

Thế giới khi chưa có DI (Dependency Injection) hay IoC (Inversion of Control) thật là phức tạp, các đối tượng kiểu như này:

HelloService helloService = HelloService(helloRepo, worldRepo) {
    this.helloRepo = helloRepo
    this.worldRepo = worldRepo
}

Muốn sử dụng đối tượng nào là cứ phải truyền qua truyền lại, truyền qua truyền lại, mà giữa mùa hè 40 độ thì cảm giác stress mỗi ngày 1 tăng lên.

Thêm vào nữa là khi làm việc với các thư viện client như Android, Flutter, React hay Unity đều là những thư viện mà các thành phần tương đối độc lập với nhau, đa phần chúng ta sẽ cần phải tạo ra một đối tượng để quản lý trạng thái chung, đối tượng này sẽ được khởi tạo một lần và sử dụng mãi mãi trong chương trình, đó cũng chính là một trong những đặc điểm và ứng dụng quan trọng của Singleton.

Mục tiêu

Để giảm áp lực cũng như tăng tuổi thọ của lập trình viên thì Singleton pattern ra đời với 2 mục tiêu:

  1. Tạo ra một đối tượng duy nhất của một class để tránh khởi tạo nhiều đối tượng, tiết kiệm bộ nhớ và tăng hiệu năng cho chương trình
  2. Tạo ra một đối tượng chứa các thông tin được chia sẻ, cho phép dễ dàng truy cập từ bất cứ lớp, hàm nào của chương trình

Nếu chúng ta đã lập trình với spring chúng ta sẽ thấy đối tượng, ApplicationContext, nếu chúng ta đã lập trình với ezyfoxserver chúng ta sẽ biết đến đối tượng EzyServerContext đây đều là những đối tượng singleton nắm giữ tất cả thông tin được chia sẻ trong toàn bộ chương trình.

Ví dụ tạo đối tượng

Giả sử chúng ta có đối tượng singleton tên là Context, hãy thay đổi helloService một chút xem sao:

HelloService helloService = HelloService() {
    this.helloRepo = Context.getInstance().getSingleton(HelloRepo.class)
    this.worldRepo = Context.getInstance()..getSingleton(WorldRepo.class)
}

Có vẻ việc phải truyền qua, truyền lại các đối tượng đã biết mất và chúng ta đã dễ thở hơn một chút đúng không?

Ví dụ chia sẻ trạng thái

Giả sử chúng ta đang xây dựng một ứng dụng android, và chúng ta cần chia sẻ thông tin của user ở tất cả các màn hình, chúng ta sẽ cần khởi tạo đối tượng StateManager như sau:

public final class StateManager {
    private static final StateManager INSTANCE = new StateManager();

    private UserProfile userProfile;
    private StateManager() {}

    public static StateManager getInstance() {
        return INSTANCE;
    }

    public void setUserProfile(UserProfile userProfile) {
        this.userProfile = userProfile;
    }

    public UserProfile getUserProfile() {
        return this.userProfile;
    }
}

Và khi sử dụng sẽ thế này:

public class MessageActivity extends Activity {
    public void onCreate(Bundle bundle)) {
        super.oCreate(bundle);
        // các code khơi tạo khác
        UserProfile user = StateManager.getInstance().getUserProfile();
        nameTextView.setText(user.getName());
    }
}

public class ProfileActivity extends Activity {
    public void onCreate(Bundle bundle)) {
        super.oCreate(bundle);
        // các code khơi tạo khác
        UserProfile user = StateManager.getInstance().getUserProfile();
        nameTextView.setText(user.getName());
    }
}

Kết luận

Ngày này với sự ra đời của các thư viện hỗ trợ DI IoC như spring, ezyfoxbean (ezyfox-bean) thì chúng ta không còn phải quan tâm nhiều đến Singleton pattern nữa, chỉ việc dùng annotation @Component, @Autowirte hay @EzySingleton@EzyAutoBind mọi việc đã được tự động giải quyết. Tuy nhiên biết thêm 1 thứ không bao giờ là thừa, nó sẽ giúp chúng ta hiểu rõ hơn cách mà các thư viện vận hành, và biết đâu 1 lúc nào đó chúng ta lại cần dùng đến.

Tham khảo

  1. wiki
  2. Ví dụ