Фабричный метод (Factory Method) — шаблон проектирования

16 января 2020
Шаблон определяет в родительском классе общую схему создания объекта и даёт дочерним классам  право самостоятельно решать, объект какого класса им нужно создавать. Тем самым, в родительском классе задаётся абстрактная логика, а в дочерних классах реализуется конкретная логика.

В шаблоне имеются две группы классов – фабрики (создатели) и продукты (результат работы фабрик/создателей). У каждой группы есть слой абстракции, который реализуется в виде интерфейсов и абстрактных классов. Фабрики создают объекты продуктов с помощью метода, который так и называется – фабричный метод. 

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

Далее приводится реализация паттерна на языке PHP (скачать исходники — ZIP, 2 Кб)

log_classes.php

Cодержит классы для работы с логами. Объекты данных классов будут являться продуктами фабрик. Перечень классов:
  • Log – абстрактный класс, который описывает абстрактную логику для работы с логами. Класс объявляет метод write для записи лога, а так же объявляет и определяет метод outNotificatonSuccess для вывода уведомления об успешном действии по записи лога (метод чисто демонстративный);
  • Log2Console, Log2DB, Log2File – классы, которые на основе абстрактного класса Log реализуют конечную логику по работе с логами. В частности, каждый класс по-своему определяет метод writeLog2Console лог выводится в консоль, у Log2DB лог пишется в БД, а у Log2File лог пишется в файл). 
<?

/**
 * Абстрактный класс для работы с логами
 */
abstract class Log 
{
    /**
     * Записывает сообщение с логом
     * @param string $message - текст с сообщением лога
     * @return bool - вернёт true в случае успешной записи лога и false в противном случае
     */
    abstract public function write($message);

    /**
     * Выводит уведомление об успешном выполнении действия
     */
    public function outNotificatonSuccess()
    {
        echo 'Действие успешно выполнено ('.get_class($this).')'."\n";
    }
}

/**
 * Класс для вывода лога на экран
 */
class Log2Console extends Log
{
    /**
     * Выводит на экран сообщение с логом
     * 
     * @param string $message - текст с сообщением лога
     * @return bool
     */
    public function write($message)
    {
        echo $message."\n";
        $this->outNotificatonSuccess();
        return true;
    }
}

/**
 * Класс для записи лога в БД
 */
class Log2DB extends Log
{
    /**
     * Записывает сообщение с логом
     * 
     * @param string $message - текст с сообщением лога
     * @return bool - вернёт true в случае успешной записи лога и false в противном случае
     */
    public function write($message)
    {
        //Добавление записи в логом в таблицу базы данных
        //...

        //Вывод уведомления об успешном выполнении действия
        $this->outNotificatonSuccess();
    }
}

/**
 * Класс для записи лога в файл
 */
class Log2File extends Log
{
    /**
     * @var string $filePath - путь к файлу, в конец которого будет записана строка с сообщением лога
     */
    private $filePath = '';

    /**
     * Задаёт путь к файлу, в который будут писаться логи
     * @param string $filePath - путь к файлу, в конец которого будет записана строка с сообщением лога
     */
    public function setFilePath($filePath)
    {
        $this->filePath = $filePath;
    }

    /**
     * Записывает сообщение с логом в конец файла
     * 
     * @param string $message - текст с сообщением лога
     * @return bool - вернёт true в случае успешной записи лога и false в противном случае
     */
    public function write($message)
    {
        if ($this->filePath == '') {
            throw new Exception('Ошибка. Не задан путь к файлу, в который будут писаться логи');
        }

        //Открытие файла и последуюющая запись строки с логом в конец файла
        $fh = fopen($this->filePath, 'a');
        if ($fh) {
            fwrite($fh, "\n".$message);
            fclose($fh);

            $this->outNotificatonSuccess();
            return true;
        } else {
            return false;
        }
    }
}

?>

log_creators.php

Файл с классами фабрик, содержит следующие элементы:
  • FactoryLog – интерфейс фабрик. В нашем случае интерфейс объявляет только один фабричный метод create;
  • CreatorLog2Console  – класс фабрики, которая определяет (реализует) фабричный метод create и создаёт продукт класса Log2Console
  • CreatorLog2DB  – класс фабрики, которая определяет (реализует) фабричный метод create и создаёт продукт класса Log2DB
  • CreatorLog2File  – класс фабрики, которая определяет (реализует) фабричный метод create и создаёт продукт класса Log2File
<?
//Подключаем классы для работы с логами
require_once(__DIR__.'/log_classes.php');

/**
 * Интерфейс для работы с создателями логов
 */
interface FactoryLog 
{
    /**
     * Создаёт и возвращат объект для работы с логами
     * @return Log
     */
    public function create();
}

/**
 * Класс для создания объекта LogConsole по работе с логами, выводящимися на экран
 */
class CreatorLog2Console implements FactoryLog
{
    /**
     * Создаёт и возвращает объект лога
     * @return Log2Console
     */
    public function create()
    {
        return new Log2Console();
    }
}

/**
 * Класс для создания объекта LogDB по работе с логами, сохраняющимися в базу данных
 */
class CreatorLog2DB implements FactoryLog
{
    /**
     * Создаёт и возвращает объект лога
     * @return Log2DB
     */
    public function create()
    {
        return new Log2DB();
    }
}

/**
 * Класс для создания объекта LogDB по работе с логами, сохраняющимися в файл
 */
class CreatorLog2File implements FactoryLog
{
    /**
     * Создаёт и возвращает объект лога
     * @return Log2File
     */
    public function create()
    {
        return new Log2File();
    }
}

?>

test.php

Скрипт с примером использования логов. Тут фабрики CreatorLog2Console, CreatorLog2DB и CreatorLog2File с помощью фабричного метода create создают продукты (объекты) по работе с логами и далее в каждом объекте вызывается метод write для записи лога. Обратите внимание, что у объекта класса CreatorLog2File дополнительно вызывается метод setFilePath для указания файла, в который будет записываться лог.
<?
//Подключаем классы для создания объектов по работе с логами
require_once(__DIR__.'/log_creators.php');

$log = CreatorLog2Console::create();
$log->write('Тестовая запись лога 1');

$log = CreatorLog2DB::create();
$log->write('Тестовая запись лога 2');

$log = CreatorLog2File::create();
$log->setFilePath(__DIR__.'/journal.txt');
$log->write('Тестовая запись лога 3');

?>

Результат работы скрипта test.php:
Тестовая запись лога 1
Действие успешно выполнено (Log2Console)
Действие успешно выполнено (Log2DB)
Действие успешно выполнено (Log2File)