Статьи

Шаблоны проектирования: шаблон Singleton

В этой статье вы узнаете, как реализовать шаблон проектирования Singleton и почему и когда использовать этот шаблон в своем приложении. Как следует из названия «Singleton», этот метод позволяет нам создавать один и только один объект класса.

Давайте посмотрим, что мы имеем в Википедии об этом шаблоне проектирования:

Шаблон singleton — это шаблон проектирования, который ограничивает создание экземпляра класса одним объектом. Это полезно, когда ровно один объект необходим для координации действий в системе.

Как упоминалось в приведенном выше определении, когда мы хотим убедиться, что для любого класса необходимо создать один и только один объект, мы должны реализовать шаблон Singleton для этого класса.

Вы можете спросить, почему мы должны реализовывать такой класс, который позволяет нам создавать только один объект этого класса. Я бы сказал, что во многих случаях мы можем применить этот шаблон проектирования. К ним относятся: класс конфигурации, класс сеанса, класс базы данных и многое другое.

Я возьму пример класса базы данных для этой статьи. Сначала мы увидим, в чем проблема, если шаблон Singleton не реализован для такого класса.

Представьте себе очень простой класс соединения с базой данных, который создает соединение с базой данных, как только мы создаем объект этого класса.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
class database {
     
    private $dbName = null, $dbHost = null, $dbPass = null, $dbUser = null;
     
    public function __construct($dbDetails = array()) {
         
        $this->dbName = $dbDetails[‘db_name’];
        $this->dbHost = $dbDetails[‘db_host’];
        $this->dbUser = $dbDetails[‘db_user’];
        $this->dbPass = $dbDetails[‘db_pass’];
 
        $this->dbh = new PDO(‘mysql:host=’.$this->dbHost.’;dbname=’.$this->dbName, $this->dbUser, $this->dbPass);
         
    }
     
}

В приведенном выше примере кода вы можете видеть, что он будет устанавливать соединение с базой данных каждый раз, когда вы создаете объект этого класса. Поэтому, если разработчик создал объект этого класса в нескольких местах, представьте количество (идентичных) соединений с базой данных, которые он создаст с сервером базы данных.

Поэтому по незнанию разработчик допускает ошибки, которые приводят к огромному влиянию на скорость работы базы данных и сервера приложений. Давайте посмотрим на то же самое, создав другой объект этого класса.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$dbDetails = array(
        ‘db_name’ => ‘designpatterns’,
        ‘db_host’ => ‘localhost’,
        ‘db_user’ => ‘root’,
        ‘db_pass’ => ‘mysqldba’
);
 
$db1 = new database($dbDetails);
var_dump($db1);
$db2 = new database($dbDetails);
var_dump($db2);
$db3 = new database($dbDetails);
var_dump($db3);
$db4 = new database($dbDetails);
var_dump($db4);
 
// Output
object(database)[1]
  private ‘dbName’ => string ‘designpatterns’ (length=14)
  private ‘dbHost’ => string ‘localhost’ (length=9)
  private ‘dbPass’ => string ‘mysqldba’ (length=8)
  private ‘dbUser’ => string ‘root’ (length=4)
  public ‘dbh’ => object(PDO)[2]
object(database)[3]
  private ‘dbName’ => string ‘designpatterns’ (length=14)
  private ‘dbHost’ => string ‘localhost’ (length=9)
  private ‘dbPass’ => string ‘mysqldba’ (length=8)
  private ‘dbUser’ => string ‘root’ (length=4)
  public ‘dbh’ => object(PDO)[4]
object(database)[5]
  private ‘dbName’ => string ‘designpatterns’ (length=14)
  private ‘dbHost’ => string ‘localhost’ (length=9)
  private ‘dbPass’ => string ‘mysqldba’ (length=8)
  private ‘dbUser’ => string ‘root’ (length=4)
  public ‘dbh’ => object(PDO)[6]
object(database)[7]
  private ‘dbName’ => string ‘designpatterns’ (length=14)
  private ‘dbHost’ => string ‘localhost’ (length=9)
  private ‘dbPass’ => string ‘mysqldba’ (length=8)
  private ‘dbUser’ => string ‘root’ (length=4)
  public ‘dbh’ => object(PDO)[8]

Если вы видите выходные данные приведенного выше кода и выходные данные, вы можете видеть, что каждому объекту назначен новый идентификатор ресурса, поэтому все объекты являются совершенно новыми ссылками, следовательно, он также выделяет отдельную память. Так что по незнанию наше приложение будет занимать ресурсы, которые действительно не требуются.

Мы не можем контролировать, как разработчики используют нашу базовую среду. Он находится под нашим контролем после процесса проверки кода, но во время разработки мы не можем постоянно поддерживать их.

Чтобы преодолеть такую ​​ситуацию, мы должны сделать наш базовый класс таким образом, чтобы он не мог создавать несколько объектов класса; вместо этого он даст уже созданный объект, если таковой имеется. Это тот случай, когда мы должны рассмотреть возможность разработки шаблона Singleton для наших базовых классов.

При реализации этого шаблона наша цель будет состоять в том, чтобы разрешить создание объекта класса один и только один раз. Позвольте мне добавить код класса ниже, а затем мы рассмотрим каждую часть этого класса.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class database {
     
    private $dbName = null, $dbHost = null, $dbPass = null, $dbUser = null;
    private static $instance = null;
     
    private function __construct($dbDetails = array()) {
         
        // Please note that this is Private Constructor
         
        $this->dbName = $dbDetails[‘db_name’];
        $this->dbHost = $dbDetails[‘db_host’];
        $this->dbUser = $dbDetails[‘db_user’];
        $this->dbPass = $dbDetails[‘db_pass’];
 
        // Your Code here to connect to database //
        $this->dbh = new PDO(‘mysql:host=’.$this->dbHost.’;dbname=’.$this->dbName, $this->dbUser, $this->dbPass);
    }
     
    public static function connect($dbDetails = array()) {
         
        // Check if instance is already exists
        if(self::$instance == null) {
            self::$instance = new database($dbDetails);
        }
         
        return self::$instance;
         
    }
     
    private function __clone() {
        // Stopping Clonning of Object
    }
     
    private function __wakeup() {
        // Stopping unserialize of object
    }
     
}

Существует мало признаков того, что вышеуказанный класс является классом Singleton. Самое первое — это закрытый конструктор, который предотвращает создание объектов с помощью ключевого слова new. Другим показателем является одна статическая переменная-член, которая содержит ссылку на уже созданный объект.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$dbDetails = array(
        ‘db_name’ => ‘designpatterns’,
        ‘db_host’ => ‘localhost’,
        ‘db_user’ => ‘root’,
        ‘db_pass’ => ‘mysqldba’
);
 
$db1 = database::connect($dbDetails);
var_dump($db1);
$db2 = database::connect($dbDetails);
var_dump($db2);
$db3 = database::connect($dbDetails);
var_dump($db3);
$db4 = database::connect($dbDetails);
var_dump($db4);
 
// Output
 
object(database)[1]
  private ‘dbName’ => string ‘designpatterns’ (length=14)
  private ‘dbHost’ => string ‘localhost’ (length=9)
  private ‘dbPass’ => string ‘mysqldba’ (length=8)
  private ‘dbUser’ => string ‘root’ (length=4)
  public ‘dbh’ => object(PDO)[2]
object(database)[1]
  private ‘dbName’ => string ‘designpatterns’ (length=14)
  private ‘dbHost’ => string ‘localhost’ (length=9)
  private ‘dbPass’ => string ‘mysqldba’ (length=8)
  private ‘dbUser’ => string ‘root’ (length=4)
  public ‘dbh’ => object(PDO)[2]
object(database)[1]
  private ‘dbName’ => string ‘designpatterns’ (length=14)
  private ‘dbHost’ => string ‘localhost’ (length=9)
  private ‘dbPass’ => string ‘mysqldba’ (length=8)
  private ‘dbUser’ => string ‘root’ (length=4)
  public ‘dbh’ => object(PDO)[2]
object(database)[1]
  private ‘dbName’ => string ‘designpatterns’ (length=14)
  private ‘dbHost’ => string ‘localhost’ (length=9)
  private ‘dbPass’ => string ‘mysqldba’ (length=8)
  private ‘dbUser’ => string ‘root’ (length=4)
  public ‘dbh’ => object(PDO)[2]

Если вы сравните выходные данные обоих разделов, то в выходных данных шаблона Singleton идентификатор ресурса для объекта будет одинаковым для всех различных объектов. Но это не тот случай, когда шаблон проектирования не используется.

Этот шаблон дизайна также называется анти-шаблоном по различным причинам, о которых я упомяну ниже:

  1. Он нарушает принцип единственной ответственности из-за качества контроля над своим творением и жизненным циклом.
  2. Он вводит глобальное состояние для вашего приложения. Я бы сказал, что глобальное состояние очень плохое, потому что любой код может изменить его значение. Так что во время отладки действительно трудно найти, какая часть кода сделала текущую стадию глобальной переменной.
  3. Синглтон, как правило, плохая идея, если вы проводите модульное тестирование, и, как правило, плохая идея не выполнять модульное тестирование.

Я изо всех сил старался объяснить шаблон проектирования Singleton, который широко обсуждается в Интернете. Я надеюсь, что вы найдете эту статью полезной. Мы рассмотрели оба аспекта этого паттерна, которые являются как паттерном дизайна, так и анти-паттерном.

Пожалуйста, оставьте свои комментарии, предложения и / или вопросы ниже, и я опубликую свой ответ как можно скорее. Вы также можете связаться со мной в Twitter @XpertDevelopers или написать мне прямо сейчас.