Design Pattern cùng Flutter. Tập 2: Singleton - "Anh là duy nhất"
Singleton là ai, địa chỉ nhà ở đâu?
Singleton là một loại mẫu thiết kế thuộc creational được ra đời nhằm khẳng định "anh là duy nhất - duy ngã độc tôn", đảm bảo rằng một lớp chỉ có một instance duy nhất, tạo ra đối tượng mà sử dụng cho toàn bộ chương trình nhưng chỉ khởi tạo một lần.
Giống như bạn đi đun nước, chỉ cần mua một cái ấm nước mà ai cũng có thể nấu nước và nấu hết lần này đến lần khác (hư thì mình đi sửa), hoặc có thể đem đi luộc cả trứng =)))
Ý tưởng chính của mẫu thiết kế này là làm cho một lớp tự chịu trách nhiệm theo dõi phiên bản duy nhất của nó. Singleton được coi là một trong những mẫu thiết kế đơn giản nhất nhưng nó cũng rất dễ mắc sai lầm nếu không cẩn thận cẩn thận.
Mục tiêu là gì, tại sao nó lại tồn tại
Mẫu thiết kế Singleton ra đời với hai mục tiêu chính:
- Tạo ra một đối tượng duy nhất, tránh việc khởi tạo nhiều đối tượng giúp tiết kiệm bộ nhớ (giống như mua 1 cái ấm nước mà có thể sử dụng cả nhà) và tăng hiệu năng cho chương trình (đỡ phải mất công đi mua cái ấm nước khác =)))) )
- Tạo ra được đối tượng có thông tin chia sẻ và có thể dễ dàng truy cập từ bất cứ đâu của chương trình
Ơ, thế thì cũng tiện quá nhỉ, vậy để có cài nhìn tổng quan hơn thì ta bắt đầu vào nhập môn UML của Singleton trước nhé.
Singleton Class Diagram
Vì series khá dài nên sẽ không có phần giới thiệu về UML, các bạn có thể vào UML Class Diagrams Reference để tham khảo thêm.
Cách tiếp cận tổng quát của Singleton được biểu diễn bằng sơ đồ lớp bên dưới. Nó chính là một lớp mà có thể tham chiếu đến chính nó:
Ta cùng điểm qua vài điểm chính của sơ đồ này nhé:
- Lớp Singleton mang một static instance và tham chiếu đến chính lớp tạo ra nó
- instance là private và chỉ có thể truy cập thông qua hàm static getInstance()
- Hàm tạo của lớp này phải là private để tránh trường hợp bị khởi tạo bên ngoài class (ấm nước trong nhà mình thì mình sử dụng thôi, ích kỉ nên không cho ai mướn cả)
Ứng dụng
Chúng ta sẽ nhắm vào những class nào tiêu tốn rất nhiều tài nguyên để khởi tạo và chỉ cần khởi tạo một lần mà có thể sử dụng cho toàn chương trình, ví dụ: khi muốn sử dụng một local database thì ta nên khởi tạo nó một lần trong xuyên suốt thời gian chạy chương trình, tránh mỗi lần lấy dữ liệu là phải khởi tạo class => lần sau khi truy cập vào database ta sẽ truy cập nhanh hơn, ít tốn nguồn tài nguyên hơn. Ngoài ra, nếu bạn muốn truy cập đi truy cập lại cùng một đối tượng, ví dụ: Logger.
Thực hành
Lý thuyết vẫn thường khô khan, nên việc thực hành nó là không thể thiếu, nên để dễ dàng hình dung thì có 3 ví dụ liên quan đến mẫu thiết kế này:
**1. Ví dụ không sử dụng Singleton **
Đầu tiên, để đơn giản nó ta chỉ cần khởi tạo một lớp trong trắng như Ngọc Trinh như sau:
class ExampleWithoutSingleton {}
}
Và ở hàm main, để dễ dàng kiểm tra xem hai object trên có giống nhau không ta sử dụng hàm identical có sẵn của Dart
void main() {
var singleton1 = ExampleWithoutSingleton();
var singleton2 = ExampleWithoutSingleton();
print(identical(singleton1, singleton2)); // false
}
Kết quả là: output false đập ngay vào mặt, vì đâu phải cùng một địa chỉ, mỗi lần anh tạo tôi đều có một địa chỉ khác nhau, không giống nhau thì "anh với tôi hai người xa lạ, bốn phương trời chẳng hẹn quen nhau"
2. Ví dụ sử dụng Singleton theo lý thuyết
Hmm, ngẫm lý thuyết và nhìn sơ đồ khối, ta tạo được lớp như sau:
class ExampleByTheory {
// Đầu tiên, tạo một hàm tạo private để không cho phép tạo instance từ bên ngoài
ExampleByTheory._internal();
// Tạo một biến private static để lưu trữ instance duy nhất của lớp
static ExampleByTheory? _instance = ExampleByTheory._internal();
// Tạo một hàm getInstance để trả về instance duy nhất của lớp
static ExampleByTheory getInstance() {
return _instance ?? ExampleByTheory._internal();
}
}
Tương tự như trên, ở hàm main ta chạy code sau:
void main() {
var singleton1 = ExampleByTheory.getInstance();
var singleton2 = ExampleByTheory.getInstance();
print(identical(singleton1, singleton2)); // true
}
Và output in ra màn hình là true, vỗ tay, vậy là đã làm đúng kết quả rồi, và chỉ có thể truy cập instance thông qua hàm getInstance() thôi (vào nhà người ta thì chỉ xin phép chủ là được, rồi muốn làm gì đó làm)
**3. Ví dụ sử dụng Singleton bằng Dart pro hơn lý thuyết **
Đầu tiên, ta xét ví dụ trước và sẽ đi vào phân tích nó nhé:
class ExampleByDartWay {
// Đầu tiên, tạo một hàm tạo private để không cho phép tạo instance từ bên ngoài
ExampleByDartWay._internal();
// Tạo một biến private static để lưu trữ instance duy nhất của lớp
static ExampleByDartWay _instance = ExampleByDartWay._internal();
// Tạo một factory constructor để trả về instance duy nhất của lớp
factory ExampleByDartWay() {
return _instance;
}
}
Thoạt nhìn thì ta không thấy gì lạ, nhưng nhìn kĩ lại thì lại xuất hiện từ khoá factory, vậy nó là gì mà khiến ta code nhìn trông pro hơn. Dart cung cấp cho ta một factory constructor, dùng để triển khai một hàm tạo mà không phải lúc nào cũng tạo một phiên bản mới của lớp đó. Điều đó có nghĩa là nó sẽ tự kiểm tra xem instance có giá trị hay không để tạo mới hoặc là trả về chính nó nếu đã được khởi tạo
Kết hợp với Flutter
Trong ứng dụng Flutter, bạn có thể sử dụng các loại Singleton trên trong nhiều ngữ cảnh khác nhau như quản lý trạng thái, quản lý bộ nhớ đệm, kết nối mạng, và nhiều hơn nữa. Sử dụng các thư viện như provider, getit, và riverpod giúp việc triển khai các mẫu Singleton này dễ dàng và hiệu quả hơn, tối giản hơn và khiến code cũng look like pro
Tổng kết
Well, Vậy là xong sơ khai phần Singleton, phần tiếp theo ta sẽ tiếp cận anh chàng mẫu thiết kế tiếp theo: Adapter - người hùng thầm lặng. Mọi người cùng đón chờ nhé ^^
All rights reserved