Menu
CoddyTech

Mẫu Singleton

Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 65 trên 87.

Mẫu Singleton là một mẫu thiết kế khởi tạo, đảm bảo một lớp chỉ có một instance trong toàn bộ ứng dụng, đồng thời cung cấp một điểm truy cập toàn cục đến instance đó. Mẫu này hữu ích cho những tài nguyên cần được chia sẻ, chẳng hạn như trình quản lý cấu hình, nhóm kết nối hoặc dịch vụ ghi nhật ký.

Mẫu này hoạt động bằng cách đặt hàm khởi tạo (constructor) ở chế độ riêng tư, ngăn việc khởi tạo trực tiếp. Thay vào đó, một phương thức tĩnh (static method) kiểm soát quyền truy cập vào instance duy nhất:

public class DatabaseConnection {
    private static DatabaseConnection instance;
    
    // Hàm khởi tạo (constructor) private ngăn việc tạo instance trực tiếp
    private DatabaseConnection() {
        System.out.println("Connection created");
    }
    
    // Điểm truy cập toàn cục
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
    
    public void query(String sql) {
        System.out.println("Executing: " + sql);
    }
}

Mỗi lần gọi getInstance(), hàm đều trả về cùng một đối tượng. Instance chỉ được tạo khi có yêu cầu lần đầu. Đây gọi là khởi tạo lười (lazy initialization):

DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();

System.out.println(db1 == db2);  // true, cùng một instance

Các thành phần chính của Singleton gồm: một trường private static lưu giữ instance, một hàm khởi tạo private và một phương thức public static trả về instance. Sự kết hợp này đảm bảo rằng dù bạn yêu cầu đối tượng bao nhiêu lần, bạn luôn nhận được cùng một đối tượng.

challenge icon

Thử thách

Dễ

Hãy xây dựng trình quản lý cấu hình cho một ứng dụng bằng mẫu Singleton! Trình quản lý cấu hình sẽ đảm bảo mọi phần trong ứng dụng dùng chung các cài đặt, bất kể chúng yêu cầu truy cập bao nhiêu lần.

Bạn sẽ sắp xếp code trong hai tệp:

  • AppConfig.java: Tạo một lớp Singleton để quản lý các cài đặt cấu hình ứng dụng. AppConfig cần có:

    Một trường static private để lưu instance duy nhất.

    Một trường String private có tên appName và một trường int private có tên maxUsers.

    Một hàm khởi tạo private, gán appName thành "MyApplication" và maxUsers thành "100", sau đó in ra AppConfig initialized.

    Một phương thức getInstance() public static, chỉ tạo instance nếu instance chưa tồn tại (khởi tạo lười), rồi trả về instance đó.

    Một phương thức setAppName(String name) để cập nhật tên ứng dụng.

    Một phương thức setMaxUsers(int max) để cập nhật số người dùng tối đa.

    Một phương thức displaySettings() để in ra App: [appName], Max Users: [maxUsers].

  • Main.java: Chứng minh Singleton hoạt động chính xác! Bạn sẽ nhận hai đầu vào: tên ứng dụng mới (String) và số người dùng tối đa mới (số nguyên).

    Đầu tiên, lấy một instance của AppConfig và lưu vào biến có tên config1. Gọi displaySettings() để hiển thị các giá trị mặc định.

    Sau đó, cập nhật các cài đặt bằng giá trị đầu vào qua việc gọi setAppName và setMaxUsers trên config1.

    Lấy một instance khác và lưu vào config2. Gọi displaySettings() trên config2 để chứng minh instance này phản ánh những thay đổi đã thực hiện thông qua config1.

    Cuối cùng, in ra kết quả cho biết cả hai tham chiếu có trỏ đến cùng một đối tượng hay không: Same instance: [true/false], bằng cách so sánh config1 == config2.

Bạn sẽ nhận hai đầu vào theo thứ tự: tên ứng dụng mới (String) và số người dùng tối đa mới (số nguyên).

Thử thách này minh họa lợi ích cốt lõi của mẫu Singleton: bất kể bạn yêu cầu cấu hình bao nhiêu lần, bạn luôn nhận được cùng một instance với cùng trạng thái được chia sẻ!

Tự làm thử

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Đọc dữ liệu đầu vào
        String newAppName = scanner.nextLine();
        int newMaxUsers = scanner.nextInt();
        
        // TODO: Lấy instance đầu tiên của AppConfig và lưu vào config1
        
        // TODO: Gọi displaySettings() trên config1 để hiển thị các giá trị mặc định
        
        // TODO: Cập nhật cài đặt bằng setAppName và setMaxUsers trên config1
        
        // TODO: Lấy một instance khác và lưu vào config2
        
        // TODO: Gọi displaySettings() trên config2 để chứng minh các thay đổi được chia sẻ
        
        // TODO: In ra liệu cả hai tham chiếu có trỏ đến cùng một đối tượng hay không:
        // Dùng config1 == config2 để in "Same instance: [true/false]"
    }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Java online