Mẫu Singleton
Thuộc phần Lập trình hướng đối tượng trong hành trình Dart của Coddy. Bài học 90 trên 110.
Mẫu Singleton đảm bảo một lớp chỉ có một đối tượng duy nhất trong toàn bộ ứng dụng và cung cấp một điểm truy cập toàn cục đến đối tượng đó. Mẫu này hữu ích khi cần đúng một đối tượng để điều phối các hoạt động trong hệ thống, chẳng hạn như trình quản lý cấu hình, kết nối cơ sở dữ liệu hoặc dịch vụ ghi nhật ký.
Trong Dart, bạn có thể triển khai Singleton bằng cách kết hợp hàm khởi tạo factory với một instance tĩnh riêng tư:
class AppConfig {
static final AppConfig _instance = AppConfig._internal();
String appName = 'MyApp';
String version = '1.0.0';
// Hàm khởi tạo (constructor) có tên riêng, chỉ dùng trong lớp
AppConfig._internal();
// Hàm khởi tạo factory trả về cùng một instance
factory AppConfig() {
return _instance;
}
}
void main() {
var config1 = AppConfig();
var config2 = AppConfig();
config1.appName = 'UpdatedApp';
print(config2.appName); // UpdatedApp
print(identical(config1, config2)); // true
}Hàm khởi tạo riêng tư (constructor) _internal() ngăn việc tạo instance từ bên ngoài. Thuộc tính tĩnh _instance lưu instance duy nhất, được tạo một lần khi lớp được tải. Mỗi lần gọi AppConfig() đều trả về cùng instance này, vì vậy những thay đổi qua một tham chiếu sẽ hiển thị ở mọi nơi.
Hãy dùng Singleton một cách hạn chế, vì chúng tạo ra trạng thái toàn cục, khiến việc kiểm thử khó hơn. Chúng phù hợp nhất với các tài nguyên dùng chung thực sự cần có trạng thái nhất quán trong toàn bộ ứng dụng.
Thử thách
DễHãy xây dựng một trình quản lý cài đặt trò chơi bằng mẫu Singleton! Bạn sẽ tạo một hệ thống đảm bảo chỉ có một đối tượng của cài đặt trò chơi tồn tại trong toàn bộ ứng dụng, để mọi phần trong trò chơi đều dùng chung một cấu hình.
Bạn sẽ sắp xếp code thành hai tệp:
game_settings.dart: Tạo lớpGameSettingstriển khai mẫu Singleton. Trình quản lý cài đặt cần có:- Một đối tượng riêng tư, static và final, lưu giữ đối tượng
GameSettingsduy nhất - Một hàm khởi tạo có tên riêng tư để ngăn việc khởi tạo từ bên ngoài
- Một hàm khởi tạo factory luôn trả về cùng một đối tượng
- Ba trường cài đặt:
difficulty(String, mặc định: "Normal"),soundEnabled(bool, mặc định: true) vàplayerName(String, mặc định: "Player1") - Một phương thức
displaySettings()in ra cài đặt hiện tại theo định dạng này:Settings: [playerName] | Difficulty: [difficulty] | Sound: [on/off](in ra "on" nếu soundEnabled là true, "off" nếu là false)
- Một đối tượng riêng tư, static và final, lưu giữ đối tượng
main.dart: Nhập cài đặt trò chơi và minh họa Singleton hoạt động chính xác:- Lấy đối tượng cài đặt và đặt tên là
settings1 - Gọi
displaySettings()để hiển thị các giá trị mặc định - Thay đổi cài đặt: đổi
playerNamethành "Hero",difficultythành "Hard" vàsoundEnabledthành false - Lấy một tham chiếu khác đến cài đặt và đặt tên là
settings2 - Gọi
displaySettings()trênsettings2để chứng minh rằng nó phản ánh những thay đổi được thực hiện thông quasettings1 - In ra việc
settings1vàsettings2có giống hệt nhau hay không theo định dạng:Same instance: [true/false]
- Lấy đối tượng cài đặt và đặt tên là
Điều này minh họa lợi ích cốt lõi của Singleton: các thay đổi được thực hiện ở bất kỳ đâu trong ứng dụng đều hiển thị ngay ở mọi nơi khác, vì chỉ có một đối tượng duy nhất!
Đầu ra mong đợi:
Settings: Player1 | Difficulty: Normal | Sound: on
Settings: Hero | Difficulty: Hard | Sound: off
Same instance: trueTự làm thử
import 'game_settings.dart';
void main() {
// TODO: Lấy instance cài đặt và đặt tên là settings1
// TODO: Gọi displaySettings() để hiển thị các giá trị mặc định
// TODO: Thay đổi các cài đặt:
// - Đổi playerName thành "Hero"
// - Đổi difficulty thành "Hard"
// - Đổi soundEnabled thành false
// TODO: Lấy một tham chiếu khác đến cài đặt và đặt tên là settings2
// TODO: Gọi displaySettings() trên settings2 để chứng minh nó phản ánh các thay đổi
// TODO: In ra liệu settings1 và settings2 có phải cùng một instance không
// Định dạng: 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
1Nền tảng về lập trình hướng đối tượng
Tệp bên ngoàiThư viện và câu lệnh importGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcBiến thể hiệnKiến thức cơ bản về hàm khởi tạoÔn tập: Máy tính đơn giản4An toàn với giá trị null
Giới thiệu về an toàn với giá trị nullCó thể null và không thể nullToán tử ? và !Từ khóa late và an toàn với giá trị nullToán tử xử lý nullAn toàn với giá trị null trong lớpÔn tập: hệ thống hồ sơ người dùng7Lớp trừu tượng và interface
Lớp trừu tượngPhương thức trừu tượngInterface trong DartInterface ngầm địnhTriển khai và kế thừaNhiều interfaceÔn tập: Máy tính hình10Bộ sưu tập và kiểu tổng quát
Tổng quan về List, Set và MapBộ sưu tập an toàn về kiểuLớp tổng quátPhương thức tổng quátRàng buộc kiểu tổng quátIterable và IteratorÔn tập: Lưu trữ tổng quát2Hàm khởi tạo trong Dart
Hàm khởi tạo mặc địnhHàm khởi tạo có tênDanh sách khởi tạoHàm khởi tạo hằng sốHàm khởi tạo nhà máyHàm khởi tạo chuyển hướngÔn tập: Trình tạo hình dạng5Đóng gói
Thành viên công khai và riêng tưQuy ước tiền tố _Tính riêng tư ở cấp thư việnTìm hiểu sâu về getter và setterChe giấu thông tinÔn tập: hồ sơ học sinh8Mixin
Giới thiệu về MixinTạo MixinSử dụng nhiều MixinTừ khóa on trong MixinMixin và kế thừaMixin và giao diệnÔn tập: hệ thống động vật3Thuộc tính của lớp
Thành viên instance và thành viên tĩnhTrường final và constBiến latePhương thức và trường tĩnhGetter và setterÔn tập: trình quản lý tài khoản ngân hàng6Kế thừa
Kế thừa cơ bảnTừ khóa superGhi đè phương thứcChú thích @overrideTừ khóa lớp finalHàm khởi tạo và kế thừaÔn tập: phân cấp nhân viên9Đa hình
Khái niệm cơ bản về đa hìnhĐa hình qua giao diệnKiểm tra kiểu lúc chạyToán tử is và asTừ khóa hiệp biếnÔn tập: Bộ xử lý thanh toánTự luyện tập: Trình biên dịch Dart online