Что такое паттерн Фабрика (Factory Method)?

Ответ

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

Суть: Класс делегирует ответственность по созданию экземпляров своим подклассам.

Пример на PHP (логирование в разные каналы):

// Продукт
interface Logger {
    public function log(string $message): void;
}

// Конкретные продукты
class FileLogger implements Logger {
    private string $filePath;

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

    public function log(string $message): void {
        file_put_contents($this->filePath, date('Y-m-d H:i:s') . ': ' . $message . PHP_EOL, FILE_APPEND);
    }
}

class DatabaseLogger implements Logger {
    private PDO $connection;

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

    public function log(string $message): void {
        $stmt = $this->connection->prepare("INSERT INTO logs (message, created_at) VALUES (?, NOW())");
        $stmt->execute([$message]);
    }
}

// Создатель (Creator)
abstract class LoggerFactory {
    // Фабричный метод
    abstract public function createLogger(): Logger;

    // Бизнес-логика, использующая продукт
    public function logMessage(string $message): void {
        $logger = $this->createLogger();
        $logger->log($message);
        echo "Message logged via " . get_class($logger) . "n";
    }
}

// Конкретные создатели
class FileLoggerFactory extends LoggerFactory {
    private string $filePath;

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

    public function createLogger(): Logger {
        return new FileLogger($this->filePath);
    }
}

class DatabaseLoggerFactory extends LoggerFactory {
    private PDO $connection;

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

    public function createLogger(): Logger {
        return new DatabaseLogger($this->connection);
    }
}

// Использование
// Клиентский код работает с фабрикой через абстрактный тип
function clientCode(LoggerFactory $factory) {
    $factory->logMessage("Application started");
}

// Конфигурация фабрики зависит от среды выполнения
if ($_ENV['LOG_CHANNEL'] === 'file') {
    $factory = new FileLoggerFactory('/var/log/app.log');
} else {
    $pdo = new PDO('mysql:host=localhost;dbname=app', 'user', 'pass');
    $factory = new DatabaseLoggerFactory($pdo);
}

clientCode($factory);

Преимущества:

  • Избегает жесткой привязки к конкретным классам продуктов в основном коде.
  • Принцип единой ответственности: Код создания продукта отделен от бизнес-логики.
  • Принцип открытости/закрытости: Легко ввести новые типы продуктов, создав новый подкласс создателя.

Отличие от Simple Factory: Factory Method использует наследование и полиморфизм, в то время как Simple Factory — это обычно один статический метод, который возвращает объект на основе входного параметра.

Ответ 18+ 🔞

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

Представь, у тебя есть задача — логировать события в приложении. И логировать можно в файл, а можно в базу данных. Или ещё куда. И вот ты сидишь и думаешь: «Какого хуя мне каждый раз в коде проверять, куда писать? Создавать логгер через new в двадцати местах? Да это же пиздопроебибна!» Вот именно для этого и придумали фабричный метод.

Суть-то простая: есть главный класс (создатель), который говорит: «Я буду выполнять основную работу, но конкретный инструмент для неё пусть сделает мой наследничек». То есть он делегирует создание объекта своим детям. Умно, да? Сам начальник не парится с мелочами.

Смотри на примере, там всё правильно написано. Сначала объявляется, что вообще такое «Логгер» — это интерфейс с методом log(). Потом делаются конкретные реализации: FileLogger пишет в файл, а DatabaseLogger — в базу. Тут всё ясно, ёпта.

А вот дальше — магия. Создаётся абстрактный класс LoggerFactory. В нём есть фабричный метод createLogger(), который объявлен, но не реализован. И есть уже готовый бизнес-метод logMessage(), который этим логгером пользуется. Этот метод — готовый скелет работы, его менять не надо.

И вот появляются наследнички: FileLoggerFactory и DatabaseLoggerFactory. Их задача — простая, как пять копеек: переопределить этот самый createLogger() и вернуть из него нужный конкретный логгер. Всё, бля! Больше они нихуя не делают.

А главный плюс где? Смотри на клиентский код внизу. Функция clientCode() принимает на вход абстрактную фабрику LoggerFactory. Ей похуй, какая именно фабрика пришла! Она просто вызывает logMessage(), а фабрика внутри себя уже создаст правильный логгер. Это и есть «избегание жесткой привязки». Красота, ядрёна вошь!

И чем это лучше Simple Factory? Да тем, что тут используется нормальное ООП — наследование и полиморфизм. Simple Factory — это часто просто статический метод-переключатель по if/else, который, если накосячить, превращается в манду с ушами, которую невозможно нормально расширить. А здесь чтобы добавить логирование, скажем, в Elasticsearch, ты просто создаёшь новый класс ElasticLoggerFactory и наследуешься от LoggerFactory. Принцип открытости/закрытости в действии — старый код не трогаешь, новый подключаешь. Доверия ебать ноль к тем, кто лезет в работающий код с правками.

Короче, паттерн овердохуища полезный, когда у тебя есть общий каркас алгоритма, но конкретный тип объекта, который в нём используется, может меняться. Не надо везде плодить switch или кучу if. Создал нужную фабрику в одном месте (например, при старте приложения, в зависимости от конфига) и дальше работаешь с абстракцией. Волнение ебать отпадает, когда нужно что-то поменять или протестировать.

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