BackendBit

К списку паттернов

Factory Method – это метод, объявленный в родительском классе или интерфейсе, который определяет способ создания объектов определённого типа, но позволяет подклассам решать, объект какого конкретного класса будет создан.

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

UML диаграмма классов паттерна Фабричный методПоказывает абстрактный класс TransactionProcessor с фабричным методом createTransaction, интерфейс Transaction, конкретные процессоры CreditCardTransactionProcessor и CryptoTransactionProcessor, а также конкретные транзакции CreditCardTransaction и CryptoTransaction.<<abstract>>TransactionProcessor+processTransaction()#createTransaction()<<interface>>Transaction+process()CreditCardTransactionProcessorCryptoTransactionProcessorCreditCardTransactionCryptoTransactionСоздаётCreditCardTransactionСоздаётCryptoTransactionВозвращает

Пример

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

package payments;

import java.math.BigDecimal;

public interface Transaction {
    void process(String sourceAccount, String destinationAccount, BigDecimal amount);
}
java
package payments;

import java.math.BigDecimal;

public final class CreditCardTransaction implements Transaction {
    @Override
    public void process(String sourceAccount, String destinationAccount, BigDecimal amount) {
        System.out.println("Processing credit card transaction: " + amount);
        // Логика обработки карточной транзакции
    }
}
java

Теперь создадим абстрактный класс процессора транзакций с фабричным методом.

package payments;

import java.math.BigDecimal;

public abstract class TransactionProcessor {
    private static final String EVENT_PROCESSING_STARTED = "transaction_processing_started";
    private static final String EVENT_PROCESSING_FINISHED = "transaction_processing_finished";

    private final EventDispatcher eventDispatcher;

    protected TransactionProcessor(EventDispatcher eventDispatcher) {
        this.eventDispatcher = eventDispatcher;
    }

    public void processTransaction(String source, String destination, BigDecimal amount) {
        Transaction transaction = createTransaction();

        eventDispatcher.dispatch(EVENT_PROCESSING_STARTED);

        transaction.process(source, destination, amount);

        eventDispatcher.dispatch(EVENT_PROCESSING_FINISHED);
    }

    // Фабричный метод
    protected abstract Transaction createTransaction();
}
java

Класс TransactionProcessor отвечает за общую логику обработки транзакций. Она едина для всех видов транзакций:

  1. Создаём конкретный вид транзакции;
  2. Отправляем событие «обработка транзакции началась»;
  3. Обрабатываем транзакцию;
  4. Отправляем событие «обработка транзакции завершилась».

Обратим внимание на шаг 1. Классу TransactionProcessor не важно, объект какого конкретного класса транзакции будет создан. Главное, чтобы класс реализовал интерфейс Transaction. Поэтому TransactionProcessor делегирует создание объекта подклассам через фабричный метод createTransaction.

Такой подход позволяет не дублировать общую логику во всех классах конкретных транзакций.

Теперь создадим конкретный процессор для карточных транзакций.

package payments;

public final class CreditCardTransactionProcessor extends TransactionProcessor {

    public CreditCardTransactionProcessor(EventDispatcher eventDispatcher) {
        super(eventDispatcher);
    }

    @Override
    protected Transaction createTransaction() {
        return new CreditCardTransaction();
    }
}
java

Появляется новое требование: приложение должно поддерживать криптовалютные транзакции. В нашу архитектуру будет легко встроить новый код. Достаточно написать новый класс транзакции и нового наследника TransactionProcessor.

package payments;

import java.math.BigDecimal;

public final class CryptoTransaction implements Transaction {
    @Override
    public void process(String sourceAccount, String destinationAccount, BigDecimal amount) {
        System.out.println("Processing crypto transaction: " + amount);
        // Логика обработки криптовалютной транзакции
    }
}
java
package payments;

public final class CryptoTransactionProcessor extends TransactionProcessor {

    public CryptoTransactionProcessor(EventDispatcher eventDispatcher) {
        super(eventDispatcher);
    }

    @Override
    protected Transaction createTransaction() {
        return new CryptoTransaction();
    }
}
java

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

package payments;

import java.math.BigDecimal;

public class PaymentApp {
    public static void main(String[] args) {
        EventDispatcher dispatcher = new EventDispatcher();

        // Выбираем нужный процессор в зависимости от типа транзакции
        TransactionProcessor processor = new CreditCardTransactionProcessor(dispatcher);
        // или: TransactionProcessor processor = new CryptoTransactionProcessor(dispatcher);

        processor.processTransaction("ACC-001", "ACC-002", new BigDecimal("100.00"));
    }
}
java

Отличие от Abstract Factory

Factory Method и Abstract Factory – родственные паттерны, которые легко спутать.

Factory Method – это один метод в базовом классе или интерфейсе. Подклассы (или реализации интерфейса) переопределяют этот метод, чтобы создавать конкретную реализацию продукта.

Abstract Factory – это интерфейс с несколькими фабричными методами для создания семейства связанных продуктов. Ключевое отличие: продукты спроектированы для совместной работы.

package factorymethod;

import java.util.Iterator;

// Factory Method: один метод в базовом классе
abstract class TransactionProcessor {
    protected abstract Transaction createTransaction();
}

// Тоже Factory Method: метод в интерфейсе
interface Iterable<T> {
    Iterator<T> iterator();
}

// А это уже Abstract Factory: интерфейс с несколькими методами
// Методы создают продукты из одного семейства (здесь это умные устройства)
interface SmartHomeFactory {
    RobotVacuumCleaner createRobotVacuumCleaner();
    SmartBulb createSmartBulb();
    SmartSocket createSmartSocket();
}
java

Если нужно создать один объект и логика создания варьируется в подклассах – используйте Factory Method. Если нужно создать группу объектов, которые должны работать вместе – используйте Abstract Factory.

Отличие от Static Factory

Static Factory (статическая фабрика) – не паттерн GoF, а идиома из книги «Effective Java». Несмотря на похожее название, это другая техника.

Factory Method завязан на наследование или имплементацию интерфейса: базовый класс (или интерфейс) объявляет абстрактный метод, подклассы его переопределяют.

Static Factory – просто статический метод, который возвращает экземпляр класса.

package staticfactory;

// Factory Method: подклассы переопределяют метод
abstract class TransactionProcessor {
    protected abstract Transaction createTransaction();
}

class CreditCardProcessor extends TransactionProcessor {
    @Override
    protected Transaction createTransaction() {
        return new CreditCardTransaction();
    }
}

// Static Factory: обычный статический метод
class Transaction {
    protected Transaction() {}

    public static Transaction creditCard() {
        return new CreditCardTransaction();
    }

    public static Transaction crypto() {
        return new CryptoTransaction();
    }
}
java

Зачем нужен Static Factory? У него есть преимущества перед конструктором.

  • Метод можно назвать как угодно. У конструктора нет имени – new Config(), а статический метод может иметь говорящее имя: Config.fromJson(), Config.empty(), Config.withDefaultSettings().

  • Метод может возвращать кэшированный объект. Например, Integer.valueOf() в Java не создаёт новый объект для чисел от -128 до 127 – возвращает уже существующий, что позволяет сэкономить память:

Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b);  // true – один объект из кэша
java
  • Метод может возвращать подтип класса. Например, EnumSet.of() возвращает RegularEnumSet или JumboEnumSet в зависимости от размера enum – клиент об этом не знает и работает с абстрактным классом EnumSet.
EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
// Внутри – RegularEnumSet или JumboEnumSet
java

Optional из Java – ещё один пример. Три статических метода для разных сценариев:

String x = "value";
Optional<String> present = Optional.of("value");    // возвращает Optional объект для не-null значений
Optional<String> nullable = Optional.ofNullable(x); // то же самое, только значение может быть null
Optional<String> empty = Optional.empty();          // когда нужно смоделировать отсутствие значения
java

Итого: Factory Method – когда нужен полиморфизм и подклассы сами решают, что создавать. Static Factory – когда нужна альтернатива конструктору с понятным именем, кэшированием или скрытием конкретного типа возвращаемого объекта.

Реальные примеры

Collection.iterator()

Пример Factory Method в стандартной библиотеке Java – метод iterator() в интерфейсе Collection. Каждая коллекция переопределяет этот метод, чтобы вернуть итератор, подходящий для её внутренней структуры.

import java.util.*;

public class IteratorExample {
    public void demonstrateFactoryMethod() {
        // Каждая коллекция возвращает свою реализацию Iterator
        List<String> arrayList = new ArrayList<>(List.of("a", "b", "c"));
        Iterator<String> arrayIterator = arrayList.iterator();

        List<String> linkedList = new LinkedList<>(List.of("a", "b", "c"));
        Iterator<String> linkedIterator = linkedList.iterator();

        Set<String> hashSet = new HashSet<>(Set.of("a", "b", "c"));
        Iterator<String> hashIterator = hashSet.iterator();

        // Клиентский код работает с интерфейсом Iterator
        // и не зависит от конкретной реализации коллекции
        while (arrayIterator.hasNext()) {
            System.out.println(arrayIterator.next());
        }
    }
}
java

java.net.URLStreamHandlerFactory

Интерфейс URLStreamHandlerFactory – ещё один пример Factory Method в стандартной библиотеке. Он позволяет создавать обработчики для различных протоколов (http, https, ftp и т.д.).

import java.net.*;

// URLStreamHandlerFactory – интерфейс из JDK с фабричным методом
// createURLStreamHandler(String protocol)

// Конкретная фабрика для кастомных протоколов
public class CustomProtocolHandlerFactory implements URLStreamHandlerFactory {
    @Override
    public URLStreamHandler createURLStreamHandler(String protocol) {
        if ("superprotocol".equals(protocol)) {
            return new SuperProtocolHandler();
        }
        return null;
    }
}
java
import java.net.*;

// Регистрация фабрики
public class ProtocolExample {
    public static void main(String[] args) throws Exception {
        URL.setURLStreamHandlerFactory(new CustomProtocolHandlerFactory());

        // Теперь можно использовать кастомный протокол
        URL url = URI.create("superprotocol://example.com/resource").toURL();
        URLConnection conn = url.openConnection();
    }
}
java

Метод createURLStreamHandler – это фабричный метод. Разные реализации URLStreamHandlerFactory могут создавать разные обработчики протоколов.

Spring AbstractFactoryBean

В Spring Framework паттерн Factory Method реализован в абстрактном классе AbstractFactoryBean<T>. Его метод getObject() управляет жизненным циклом объекта (синглтон или прототип), а создание делегирует абстрактному методу createInstance() – это и есть фабричный метод.

import org.springframework.beans.factory.config.AbstractFactoryBean;

// Подкласс для email-уведомлений
public class EmailSenderFactory extends AbstractFactoryBean<NotificationSender> {

    @Override
    public Class<?> getObjectType() {
        return NotificationSender.class;
    }

    @Override
    protected NotificationSender createInstance() {
        return new EmailNotificationSender();
    }
}
java
import org.springframework.beans.factory.config.AbstractFactoryBean;

// Подкласс для SMS-уведомлений
public class SmsSenderFactory extends AbstractFactoryBean<NotificationSender> {

    @Override
    public Class<?> getObjectType() {
        return NotificationSender.class;
    }

    @Override
    protected NotificationSender createInstance() {
        return new SmsNotificationSender();
    }
}
java

Использование в конфигурации Spring:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public EmailSenderFactory notificationSenderFactory() {
        return new EmailSenderFactory();
    }

    // Spring вызовет getObject(), который делегирует createInstance()
    @Bean
    public OrderService orderService(NotificationSender sender) {
        return new OrderService(sender);
    }
}
java

Структура AbstractFactoryBean повторяет структуру паттерна: базовый класс (AbstractFactoryBean) определяет общую логику в getObject(), а конкретные подклассы переопределяют фабричный метод createInstance(). Каждый подкласс создаёт свой конкретный продукт – как и предполагает паттерн.