14.08 2010

Factory method

Перед прочтением ознакомьтесь с введением в паттерны проектирования на PHP, в котором описаны принятые соглашения и понятия. Данная статья дополняется с некоторой периодичностью, так что если вы ее читали ранее, не факт что данные не изменились.

Factory Method относиться к классу порождающих паттернов. Они используются для определения и поддержания отношений между объектами. Фабричные методы избавляют проектировщика от необходимости встраивать в код зависящие от приложения классы.

Пример

Предположим мы создаем некий XML парсер, который анализирует предоставленный файл и преобразует его в DOM дерево. Каждый элемент этого дерева назовем нодой (Node). В время разбора файла, перед нами встанет задача порождения новых нод, и мы напишем там примерно такой код:

class Xml_Node()
{
    /*...*/
    public function parse()
    {
        /*...*/
        $ChildNode = new Xml_Node();
        /*...*/
    }
    /*...*/
}

Что в этом плохого? Приведу такой пример: мы захотим на основе XML файла строить структуру объектов, определенного класса, чтобы использовать ее в дальнейшем, и нам, в соответствии с принципом "до тебя уже все написано", захотелось использовать готовый класс XML_Node.

Мы делаем своего наследника XML_Node_Processor, и хотим теперь повлиять на процесс анализа файла так, чтобы при определенном теге инстанцировался определенный класс (Для тега food - My_Food, для cow - My_Big_Orange_Cow). И при реализации как приведена выше, для этого нам придется полностью перегрузить метод parse, ради того, чтобы сделать копипаст кода из родительского класса отредактировав всего одну строку кода. Согласитесь, это глупо.

Суть паттерна

Возможная реализация на PHP

abstract class XML_Node_Abstract
{
    abstract function createNode($tag);
}

class Xml_Node extends XML_Node_Abstract
{
    /*...*/
    public function createNode($tag)
    {
        return new Xml_Node();
    }
    /*...*/

    public function parse()
    {
        /*...*/
        $ChildNode = $this -> createNode($Tag);
        /*..*/
    }
}

class Xml_Node_Processor extends Xml_Node
{
    public function createNode($tag)
    {
        switch($tag)
        {
            case "food":
                return new My_Food();
            case "cow":
                return new My_Big_Orange_Cow();
        }

        return parent::createNode($tag);
    }

}

class My_Food extends Xml_Node_Processor {};
class My_Big_Orange_Cow extends Xml_Node_Processor {};

Скачать можно тут

В заключение

  • В реализации фабричного метода не всегда нужен абстрактный класс создателя (XML_Node_Abstract). На его месте может использоваться конкретный экземпляр. Из этого примера можно выкинуть XML_Node_Abstract и ничего не изменится
  • Результат возвращаемый фабричным методом, должен всегда соответствовать заданному интерфейсу (в нашем случае интерфейсу класса Xml_Node)
  • Фабричный метод может быть статической функцией, и использоваться для инстанации объектов подкласса
  • Фабричный метод не обязательно должен возвращать объект, он так же может возвращать класс. При этом все наследники и родители так же должны возвращать класс.

Паттерн проектирования Abstract Factory фактически состоит из фабричных методов

Дополнено

Вопрос

Не понял. Смысл в том, чтобы в методе parse наследников создавались экземпляры именно их, а не родителя?

Почему бы вместо:

$ChildNode = new Xml_Node ();

не сделать:

$ChildNode = new static;?

Ответ

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

Вот представьте, у вас есть такой код в нескольких местах:

$node = new Xml_Node ();
$title = $node->getTitle ();

Приходит к вам проект менеджер и говорит, что xml будут приходить в двух разных форматах. Подумаешь тоже:

if ($this -> isFormatOne ()) {
    $node = new Xml_Node ();
}
else {
    $node = new Xml_Node_Extended ();
}

$title = $node -> getTitle ();

Затем он приходит снова, и говорит, что форматов теперь будет 3,10,500. При такой архитектуре, придется КАЖДЫЙ раз вносить изменения во ВСЕ вхождения такого кода. Если же использовать фабричный метод, то придется изменить только его, а создания объекта будет выглядеть всегда одинково:

$node = $this -> createNode ();
$title = $node -> getTitle ();

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

comments powered by Disqus