Tìm hiểu về Singleton pattern.
Bài viết được tham khảo từ cuốn Design pattern for dummies
Ở bài trước, mình đã giới thiệu cho các bạn về Template pattern: https://viblo.asia/trung.nn.92/posts/ZabG91kkGzY6. Hôm nay chúng ta sẽ tìm hiểm về Singleton pattern.
Singleton Pattern là một mẫu thiết kế (design pattern) được sử dụng để bảo đảm rằng mỗi một lớp (class) chỉ có được một thể hiện (instance) duy nhất và mọi tương tác đều thông qua thể hiện này.
Singleton Pattern cung cấp một phương thức khởi tạo private, duy trì một thuộc tính tĩnh để tham chiếu đến một thể hiện của lớp Singleton này. Nó cung cấp thêm một phương thức tĩnh trả về thuộc tính tĩnh này.
Bài toán thực tế
Bạn gặp một sự cố về hiệu năng hệ thống. Cùng một thời điểm, các bạn đang sử dụng một lúc nhiều đối tượng và chúng làm tiêu tốn quá nhiều tài nguyên của hệ thống. Đây là vấn đề mà bạn cần phải khắc phục, và Singleton pattern có thể giúp bạn thực hiện được điều đó.
Tạo một đối tượng duy nhất với mẫu Singleton
Chúng ta bắt đầu với mẫu Singleton và xử lý rắc rối mà chúng ta vừa gặp phải. Chúng ta muốn chắc chắn rằng chỉ tạo duy nhất 1 đối tượng cho 1 lớp cụ thể dù cho người khác có cố gắng tạo bao nhiêu đối tượng đi nữa.
Các bạn đang tạo ra hàng trăm đối tượng Database trong mã ngu62n, và rắc rối là từng đối tượng này rất lớn. Đâu là giải pháp? Mấu duy nhất (Singeton pattern) là câu trả lời.
Mẫu duy nhất Singleton chắc chắn rằng bạn có thể khởi tạo chỉ duy nhất một đối tượng cho một lớp. Nếu bạn không sử dụng mẫu thiết kế này, toán tử new như thường sử dụng, sẽ tạo ra liên tiếp nhiều đối tượng mới như sau:
Bạn sử dụng mẫu Singleton khi bạn muốn hạn chế việc sử dụng tài nguyên hoặc khi bạn phải xử lý 1 đối tượng nhạy cảm mà dữ liệu của nó không thể chia sẻ cho mọi thể hiện.
Bất cứ khi nào bạn thật sự cần duy nhất 1 thể hiện của 1 lớp, hãy nghĩ tới mẫu Singleton thay vì dùng toán tử new.
Tạo một lớp Database theo kiểu Singleton
Chúng ta bắt đầu vào xây dựng lớp Database:
class Database { private $record; private $name; public function Database($name) { $this->name = $name; $this->record = 0; } … }
Bạn cần thêm vào 2 hàm editRecord(), cho phép bạn chỉnh sửa 1 bản ghi và hàm getName() để lấy ra tên của Database.
class Database { private $record; private $name; public function Database($name) { $this->name = $name; $this->record = 0; } public function getName() { return $this->name; } public function editRecord($operation) { return “Performing a ” . $operation . ” operation on record ” . $this->record . ” in database ” . $this->name; } }
Tới giờ mọi việc vẫn tốt đẹp. Bất cứ khi nào bạn tạo 1 đối tượng bằng toán tử new, một đối tượng mới sẽ được tạo ra. Nếu bạn tạo 3 database, bạn sẽ có 3 đối tượng như sau:
$dataOne = new Database(“Product”); $dataTwo = new Database(“Product also”); $dataThree = new Database(“Product again”);
Làm sao để bạn có thể tránh việc tạo 1 đối tượng khi mới sử dụng toán tử new? Đây là một giải pháp: làm cho hàm khởi tạo trở nên private:
private function Database($name) { $this->name = $name; $this->record = 0; }
Với cách làm này chúng ta không thể gọi được hàm khởi tạo (contructor) của nó, tức là không thực hiện được dòng lệnh này: $data = new Database(“”). Tuy nhiên nếu không gọi được hàm khởi tạo thì làm sao mà ta tạo đối tượng được? Vậy làm sao bạn có thể tạo 1 đối tượng khi bạn không thể gọi hàm khởi tạo nó?
Do đó chúng ta phải xây dựng 1 hàm nào đó để thay thế cho hàm khở tạo, và khi gọi hàm này thì đối tượng được phát sinh ra theo 1 cách đặc biệt (đặc biệt ở chỗ nếu chưa có đối tượng thì nó sẽ tạo đối tượng mới, còn nếu có rồi thì nó trỏ vào đối tượng đã có rồi, như vậy thì bảo đảm chương trình luôn tồn tại 1 đối tượng). Hàm này thường được đặt tên là getInsstance(). Chú ý rằng hàm này phải được khai báo kiểu public và static để bạn có thể truy cập tới nó thông qua tên lớp.
public static function getInstance($name) { }
Hàm này sẽ trả về một đối tượng Database, nhưng hàm chỉ hoạt động khi có ít nhất một đối tượng đã tồn tại. Vì thế đầu tiên ta cần kiểm tra đối tượng này, nếu đối tượng chưa được khởi tạo thì ta cần khởi tạo nó.
private static $singletonObject; public static function getInstance($name) { if ($this->singletonObject == null) { $this->singletonObject = new Database($name); } return $this->singletonObject; }
Vấn đề đã được giải quyết. Bây giờ chỉ có duy nhất 1 đối tượng Database tồn tại trong cùng 1 thời điểm. Việc gọi hàm getInstance() sẽ cho ta 1 đối tượng như sau:
Bây giờ, không quan tâm đến việc bạn gọi bao nhiêu lần hàm getInstance(), bạn luôn nhận được cùng 1 đối tượng. Đó chính là cách bạn làm với mẫu Singleton.
Chạy thử ví dụ với mẫu Singleton
Bắt đầu bằng việc tạo một đối tượng Database với tên là products, sau đó gọi hàm getName():
$database = Database::getInstance(“Product”); echo “This is the ” . $database->getname() . ” database.”;
Sau đó bạn tiếp tục tạo 1 đối tượng Database với tên là employees, và gọi lại hàm getName() để kiểm tra:
$database = Database::getInstance(“Employees”); echo “This is the ” . $database->getname() . ” database.”;
Tuy nhiên, đối tượng Database đã được tạo, vì vậy trong lần thứ 2, hàm getInstance () vẫn trả về đối tượng Database cũ.
Vậy là bạn đã nhận được duy nhất 1 đối tượng cho dù đã thực hiện việc tạo 2 lần. Cách thức bạn làm vệc như sau: ngăn cản việc khởi tạo bằng toán tử new, và tạo 1 hàm để tạo đối tượng theo ý bạn. Đó chính là cách hoạt động của mẫu Singleton
Vậy là chúng ta vừa tìm hiểu xong về Singleton Pattern. Hy vọng bài viết sẽ giúp ích cho các bạn trong việc lập trình.