Фабричный метод на php
дата публикации: 2016-04-21

Фабричный метод звучит достаточно грозно и отпугивает новичков, которые хотят изучить этот паттерн. Важно же не только изучить, а практически использовать его. Попробую объяснить на примерах и простыми словами. Рассмотрим следующую задачу: необходимо пересылать файлы из одного хранилища в другое по различным протоколам, к примеру ftp и sftp.

Сам класс, методы которого используются для передачи, не сложный, но он содержит методы одинаковые для разных протоколов и разные для разных протоколов. Получается, что мы должны либо создавать один класс, в котором описывать работу с разными протоколами, либо два класса, в каждом из которых реализовывать общие методы, либо...использовать паттерн фабричный метод. В первом случае мы рискуем создать класс, экземпляр которого будет занимать память, но реализовывать только часть методов и переменных этого класса, что плохо. Во втором случае мы получаем двойной код общих методов, что неудобно при внесении изменений. Конечно вы скажете, что можно создать класс-родитель, отнаследовать два дочерних класса и далее создавать экземпляры этих классов. Этот путь верный, но для большей унификации результирующего кода обычно требуют организовать создание экземпляров класса прямо из родительского класса, а не пользоваться конструкторами дочернего класса.

Создадим два файла, index.php и classes.php со следующим кодом

//файл classes.php
//установим пространство имен
namespace FileTransfer;
//используем следующую команду, для настройки глобальных исключений
use Exception;
//абстрактный класс
abstract class Product {
  //переменные, которые не доступны извне, но наследуются
  //эти переменные общие для всех протоколов
  protected $user, $password, $host, $port, $timeout, $conn;
  //открытый метод, который может быть вызван без создания экземпляра класса
  //т.к. создание экземпляра абстрактного класса невозможно
  static public function getProduct($type) {
    if ($type == 'ftp') {
      return new ftpProduct();
    } elseif ($type == 'ssh') {
      return new sftpProduct();
    }
  } 
  //общий метод для классов всех протоколов
  public function setAccessData($user, $password, $host, $port = null) {
    $this->user = $user;
    $this->password = $password;
    $this->host = $host;
    if ($port != null) {
      $this->port = $port;
    }
    print "Это общий метод для всех протоколов\n";
  }
  //методы, которые не имеют реализации, их и нужно раскрывать
  //в дочерних классах
  abstract function getConnection();
  abstract function closeConnection();
  //и т.д.
}

class ftpProduct extends Product {
  //конструктор класса
  function __construct() {
    $this->port = 21;
    $this->timeout = 30;
  }
  //реализация абстрактного метода
  public function getConnection() {
    print "Я соединился по протоколу ftp\n";
  }
  //реализация абстрактного метода
  public function closeConnection() {
    print "Я разъединился с сервером, с вами был протокол ftp\n";
  }
}

class sftpProduct extends Product {
  //конструктор класса
  function __construct() {
    $this->port = 22;
    $this->timeout = 30;
  }
  //реализация абстрактного метода
  public function getConnection() {
    print "Я соединился по протоколу ssh\n";
  }
  //реализация абстрактного метода
  public function closeConnection() {
    print "Я разъединился с сервером, с вами был протокол ssh\n";
  }
}

//файл index.php
header('Content-type: text/plain; charset=utf-8');
//подключение библиотеки
include 'classes.php';
//использование пространства имен
use FileTransfer as FT;
//вызов метода абстрактного класса (маршрутизатор и неявный создатель экземпляра)
$ftpTransfer = FT\Product::getProduct("ftp");
$sftpTransfer = FT\Product::getProduct("ssh");
//входные данные
$user = 'root';
$password = '';
$host = 'localhost';
$port = 22;
//передаем в экземпляр данные для подключения
$ftpTransfer->setAccessData($user, $password, $host);
$sftpTransfer->setAccessData($user, $password, $host, $port);
//соединяемся с сервером
$ftpTransfer->getConnection();
$sftpTransfer->getConnection();
//разъединяемся с сервером
$ftpTransfer->closeConnection();
$sftpTransfer->closeConnection();

Этот код наглядно показывает работу паттерна. Мы вызываем так называемый маршрутизатор, который создает экземпляр класса и возвращает нам ссылку на него (именно поэтому паттерн и называется фабричным методом), а далее мы уже работаем с реализацией абстрактных методов, вызываем метод соединения с сервером (метод не соединяет, он лишь демонстрирует работу), и метод разъединения.

В коде я постарался дать максимум комментариев, а сейчас, в качестве заключения проясню идейные моменты. Во-первых, не стоит использовать фабричный метод всегда и везде, так как этот паттерн зачастую не нужен и заметно усложняет код. В большинстве случаев достаточно обойтись просто наследованием. Во-вторых, так как суть этого паттерна по большей степени автоматизация создания экземпляров класса, то можно реализовать любую защиту "от дурака", так как бездумное создание экземпляров приводит к забиванию памяти, хотя для этих целей существует паттерн Одиночка, его тоже рассмотрим, чуть позже.