Singleton Pattern là gì và các ứng dụng trong PHP?

Singleton Pattern là gì và các ứng dụng trong PHP

Trong bài viết trước, chúng ta đã cùng nhau tìm hiểu về Prototype Pattern. Trong bài viết này, chúng ta sẽ tiếp tục tìm hiểu một design pattern khác cũng thuộc nhóm khởi tạo (creational patterns) mang tên “Singleton Pattern”.

Singleton Pattern là gì thế?

Singleton Pattern là một loại design pattern thuộc nhóm khởi tạo (creational patterns). Về bản chất, loại design pattern này đảm bảo 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.

Nó 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. Ngoài ra, nó cũng cung cấp thêm một phương thức tĩnh để trả về thuộc tính tĩnh này.

Singleton Pattern

Cấu trúc

Một Singleton Pattern thường là một lớp có các đặc điểm sau:

Singleton Pattern

  • Phương thức khởi tạo private để ngăn cản việc tạo thể hiện của lớp từ các lớp khác.
  • Biến private static của lớp chứa thể hiện duy nhất của lớp đó.
  • Phương thức getInstance() để trả về thể hiện của class.

Khi nào nên sử dụng Singleton Pattern?

Dưới đây là một số trường hợp chúng ta có thể cân nhắc sử dụng nó:

  • Trong trường hợp chỉ cần một thể hiện duy nhất của một lớp nào đó.
  • Khi thể hiện duy nhất khả mở thông qua việc kế thừa, người dùng có thể sử dụng thể hiện kế thừa đó mà không cần thay đổi các đoạn mã của chương trình.

Ví dụ minh họa về Singleton Pattern

<?php
     class BookSingleton {
          private $author = 'Gamma, Helm, Johnson, and Vlissides';
          private $title  = 'Design Patterns';
          private static $book = NULL;
          private static $isLoanedOut = FALSE;

          private function __construct() {
          }

          static function borrowBook() {
            if (self::$isLoanedOut == FALSE) {
              if (self::$book == NULL) {
                 self::$book = new BookSingleton();
              }
              self::$isLoanedOut = TRUE;

              return self::$book;
            } else {
              return NULL;
            }
          }

          function returnBook(BookSingleton $bookReturned)
{
              self::$isLoanedOut = FALSE;
          }

          function getAuthor() {
               return $this->author;
          }

          function getTitle() {
              return $this->title;
          }

          function getAuthorAndTitle() {
return $this->getTitle() . ' by ' . $this->getAuthor();
          }
       }

     class BookBorrower {
          private $borrowedBook;
          private $haveBook = FALSE;

          function __construct() {
          }

          function getAuthorAndTitle() {
            if ($this->haveBook == TRUE) {
              return $this->borrowedBook->getAuthorAndTitle();
            } else {
              return "I don't have the book";
            }
          }

          function borrowBook() {
            $this->borrowedBook = BookSingleton::borrowBook();
            if ($this->borrowedBook == NULL) {
              $this->haveBook = FALSE;
            } else {
              $this->haveBook = TRUE;
            }
          }

          function returnBook() {
$this->borrowedBook->returnBook($this->borrowedBook);
          }
       }

     // Initialization

     echo 'BEGIN TESTING SINGLETON PATTERN\n\n';
     $bookBorrower1 = new BookBorrower();
     $bookBorrower2 = new BookBorrower();

     $bookBorrower1->borrowBook();
     echo 'BookBorrower1 asked to borrow the book\n';
     echo 'BookBorrower1 Author and Title:\n';
     echo $bookBorrower1->getAuthorAndTitle() . '\n\n';

     $bookBorrower2->borrowBook();
     echo 'BookBorrower2 asked to borrow the book\n';
     echo 'BookBorrower2 Author and Title:\n';
     echo $bookBorrower2->getAuthorAndTitle() . '\n\n';

     $bookBorrower1->returnBook();
     echo 'BookBorrower1 returned the book\n\n';

     $bookBorrower2->borrowBook();
     echo 'BookBorrower2 Author and Title:\n';
     echo $bookBorrower1->getAuthorAndTitle() . '\n\n';

     echo 'END TESTING SINGLETON PATTERN';
?>

Kết quả thu được:

BEGIN TESTING SINGLETON PATTERN
BookBorrower1 asked to borrow the book
BookBorrower1 Author and Title:
Design Patterns by Gamma, Helm, Johnson, and Vlissides

BookBorrower2 asked to borrow the book
BookBorrower2 Author and Title:
I don't have the book

BookBorrower1 returned the book

BookBorrower2 Author and Title:
Design Patterns by Gamma, Helm, Johnson, and Vlissides

END TESTING SINGLETON PATTERN

Ưu và nhược điểm của Singleton Pattern

Ưu điểm

  • Đảm bảo rằng chỉ có duy nhất một thể hiện của một lớp nào đó được tạo ra trong suốt quá trình thực thi chương trình nhờ đó giúp tiết kiệm tài nguyên hệ thống.
  • Dễ dàng cài đặt và triển khai.

Nhược điểm

Singleton Pattern hiện được xem là một anti-pattern và cũng đang gây ra rất nhiều tranh cãi trong cộng đồng lập trình viên. Điều này xuất phát từ một số nguyên nhân sau:

  • Nó vi phạm nguyên tắc đơn nhiệm (Single Responsibility Pattern – SRP) do một lớp áp dụng pattern này vừa phải chịu trách nhiệm xử lý các nghiệp vụ của riêng nó, vừa phải chịu trách nhiệm quản lý số lượng thể hiện được tạo ra của lớp và vừa cung cấp một điểm truy cập tới thể hiện đó.
  • Singleton Pattern xoay quanh việc thao tác với biến toàn cục (global state). Hầu hết các lập trình viên, đặc biệt là các lập trình viên theo hướng lập trình hàm (functional programming) đều đồng ý rằng việc sử dụng biến toàn cục trong chương trình là một trong những hành động mang lại rủi ro cao vì trong các chương trình lớn khả năng vô tình thay đổi giá trị của biến toàn cục này là không thể tránh khỏi. Giá trị của biến toàn cục bị thay đổi đồng nghĩa với việc kết quả của các đoạn code khác có phụ thuộc vào nó cũng sẽ bị thay đổi theo.
  • Lớp được cài đặt Singleton Pattern không thể kế thừa hay mở rộng được.
  • Việc cài đặt không hợp lý có thể dẫn đến một số vấn đề phát sinh có liên quan đến luồng (thread).
  • Nó cũng gây khó khăn trong quá trình kiểm thử.

Xuất phát từ những nguyên nhân trên mà đa phần các lập trình viên đều đưa ra lời khuyên nên tránh sử dụng Singleton Pattern một cách tối đa.

Vậy còn chút lý do gì để mình sử dụng Singleton Pattern không nhỉ?

Dù được coi là một anti-pattern, Singleton Pattern vẫn có thể được sử dụng trong một số trường hợp sau:

  • Ghi log: Đa phần các lập trình viên đều đồng ý rằng việc ghi log nên được áp dụng ở nhiều nơi trong code. Singleton pattern có thể phục vụ mục đích này mà không làm tổn hại đến khả năng đọc, kiểm tra hoặc bảo trì code sau này.
  • Truy cập database hoặc các tập tin của hệ thống: Singleton Pattern cũng có thể được sử dụng để truy cập database hoặc các tập tin của hệ thống, nó đảm bảo rằng chỉ có một điểm kết nối duy nhất đến cơ sở dữ liệu cũng như các tập tin của hệ thống nhờ đó giúp tăng tính linh hoạt cũng như tiết kiệm được tài nguyên một cách đáng kể.

Kết luận

Trong bài viết này, chúng ta đã cùng nhau tìm hiểu về Singleton Pattern, hy vọng pattern này sẽ giúp ích cho các bạn trong tương lai. Trong bài viết tiếp theo của series, mình sẽ tiếp tục giới thiệu cho các bạn một design pattern mới thuộc nhóm khởi tạo mang tên “Object Pool Pattern”. Các bạn nhớ đón xem nhé!

Gửi phản hồi