Как выглядит плохой код под паттерн "Фабричный метод"?

Я изучаю паттерны и столкнулся с проблемой что не могу найти плохой код, который можно было бы переписать с использованием паттерна "Фабричный метод".

В учебных материалах найденных в гугле приводят либо абстрактные примеры, либо плохие примеры где безосновательно используется паттерн "фабричный метод".

Как я могу найти пример плохого кода, который бы явно нуждался в паттерне "Фабричный метод".

Я пытаюсь найти пример с обоснованием почему в этом примере необходимо использовать фабричный метод и почему без него будет плохо.


Ответы (2 шт):

Автор решения: sanmai

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

Например, если за создание объекта отвечает фабрика, то объекту-пользователю во время тестирования можно через DI подсунуть подставной Mock-объект, дальше используя паттерн Spy для проверки количества и качества вызываемых объектом-пользователем методов. Если вы не пользуетесь фабрикой, а отдаёте создание объектов самим пользователям, то вам будет очень сложно или невозможно провести такое глубокое юнит-тестирование. Без этого паттерна скорее всего вам придётся ограничиться интеграционным тестированием.

Иначе говоря, фабричный метод и родственные паттерны стоит использовать на практике не столько потому, что без них никак нельзя, а сколько потому что с ними программа будет лучше и глубже протестирована, а значит вы будете спать лучше и ваш CI скорей найдёт ошибки, которые иначе можно пропустить.

→ Ссылка
Автор решения: Mark Shevchenko

Часто преимущества объектно-ориентированного программирования подают на примере геометрических фигур.

interface Figure
{
    public function square();
}

class Circle implements Figure
{
    private $radius;

    public function __construct($radius) {
        $this->radius = $radius;
    }

    public function square() {
        return $this->radius ** 2 * pi();
    }
}

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

Поскольку в разных фигурах нужно сохранять разные наборы параметров, мы определяем полиморфные методы для сохранения и загрузки. В PHP есть функции pack и unpack, которые позволяют сложить несколько переменных в одну строку — сериализовать, и затем извлечь их обратно — десериализовать.

interface Figure
{
    public function square();
    public function save();
    public function load($data);
}

class Circle implements Figure
{
    private $radius;

    public function __construct($radius = 0.0) {
        $this->radius = $radius;
    }

    public function square() {
        return $this->radius ** 2 * pi();
    }

    public function save() {
        return pack('E', $this->radius);
    }

    public function load($data) {
        $unpack = unpack('Eradius', $data);
        $this->radius = $unpack['radius'];
    }
} 

Сохранение массива фигур в поток будет очень простой задачей:

function save($handle, $figures) {
    foreach ($figures as $figure) {
        $className = get_class($figure);
        $data = $figure->save();
        fputs("$className\n");
        fputs("$data\n");
    }
}

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

function load($handle) {
    $figures = [];

    while (!feof($handle)) {
        $className = trim(fgets($handle));

        switch ($className) {
            case 'Circle':
                $figure = new Circle();
                break;

            . . .
        }

        $data = trim(fgets($handle));
        $figure->load($data);

        $figures[] = $figure;
    }

    return $figures;
}

Мы убрали обязательный параметр из конструктора окружности, чтобы все конструкторы вызывались одинаково. Семантика нового кода такова: сначала мы создаём объект в некотором пустом состоянии, а потом восстанавливаем состояние из ранее сохранённых данных.

Мы видим, что методы сохранения и загрузки несимметричны. Если мы добавим десять новых фигур, метод save останется неизменным, в отличие от load. Впрочем, сам метод load тоже неоднороден — внутри явно выделяется громоздкая конструкция switch.

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

Очевидно, её было бы неплохо вынести в отдельный метод или в отдельный ассоциативный массив. Паттерн фабричный метод можно реализовать чуть по разному в разных языках программирования.

Например, в классическом Java всё должно быть явно объектом, поэтому фабрика тоже должна быть объектом. В PHP это можно описать так:

$factories = array(
    'Circle' => new CircleFactory(),
    'Rectangle' => new RectangleFactory(),
    'Ellipse' => new EllipseFactory(),
    . . .
);

. . .

$factory = $factories[$className];
$figure = $factory->create();

В C++ можно писать в процедурном стиле, поэтому там можно не создавать объекты фабрик, а просто использовать старые добрые функции.

Код на PHP:

function createCircle() {
    return new Circle();
}

function createRectrangle() {
    return new Rectangle();
}

$factories = array(
    'Circle' => 'createCircle',
    'Rectangle' => 'createRectangle',
    . . .
);

. . .

$factory = $factories[$className];
$figure = $factory();

Точно также мы могли бы использовать лямбда-функции. Но самый простой для PHP способ это создание объектов по имени класса:

$figure = new $className();

Очень удобно. Конечно это не фабричный метод в том виде, в котором он описан в литературе, но классический фабричный метод больше подходит для языков со статической типизацией, таких как Java и C++. В языках с динамической типизацией есть свои небольшие плюсы.

Код загрузки теперь выглядит так:

function load($handle) {
    $figures = [];

    while (!feof($handle)) {
        $className = trim(fgets($handle));
        $data = trim(fgets($handle));

        $figure = new $className();
        $figure->load($data);

        $figures[] = $figure;
    }

    return $figures;
}

Этот метод по сложности такой же, как и метод save. При добавлении новых фигур нам больше не нужно его переписывать.

→ Ссылка