Фабричный метод на 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();
Этот код наглядно показывает работу паттерна. Мы вызываем так называемый маршрутизатор, который создает экземпляр класса и возвращает нам ссылку на него (именно поэтому паттерн и называется фабричным методом), а далее мы уже работаем с реализацией абстрактных методов, вызываем метод соединения с сервером (метод не соединяет, он лишь демонстрирует работу), и метод разъединения.
В коде я постарался дать максимум комментариев, а сейчас, в качестве заключения проясню идейные моменты. Во-первых, не стоит использовать фабричный метод всегда и везде, так как этот паттерн зачастую не нужен и заметно усложняет код. В большинстве случаев достаточно обойтись просто наследованием. Во-вторых, так как суть этого паттерна по большей степени автоматизация создания экземпляров класса, то можно реализовать любую защиту "от дурака", так как бездумное создание экземпляров приводит к забиванию памяти, хотя для этих целей существует паттерн Одиночка, его тоже рассмотрим, чуть позже.