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 instanceCá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.
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.AppConfigcần có:Một trường static private để lưu instance duy nhất.
Một trường String private có tên
appNamevà một trường int private có tênmaxUsers.Một hàm khởi tạo private, gán
appNamethành"MyApplication"vàmaxUsersthành"100", sau đó in raAppConfig 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 raApp: [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
AppConfigvà lưu vào biến có tênconfig1. GọidisplaySettings()để 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
setAppNamevàsetMaxUserstrênconfig1.Lấy một instance khác và lưu vào
config2. GọidisplaySettings()trênconfig2để chứng minh instance này phản ánh những thay đổi đã thực hiện thông quaconfig1.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ánhconfig1 == 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]"
}
}
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
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)11Mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu BuilderMẫu ObserverMẫu Strategy3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online