Перейти к содержанию

Factory Method (Фабричный метод)

Категория: порождающий паттерн.

Проблема

Базовый класс реализует общий алгоритм, но одна из его частей - создание конкретного объекта - должна отличаться в зависимости от контекста, и заранее (на этапе написания базового класса) неизвестно, какой именно конкретный тип понадобится.

Если "зашить" в базовый класс прямое создание конкретного продукта (new ConcreteProduct()), базовый класс окажется жёстко привязан к этой конкретной реализации и не сможет быть переиспользован для других вариантов продукта без изменения своего кода.

Решение

  1. Выделяется общий интерфейс (или базовый класс) для создаваемых объектов - продукт.
  2. В базовом классе-создателе создание продукта выносится в отдельный виртуальный/абстрактный метод ("фабричный метод").
  3. Подклассы-создатели переопределяют этот метод, возвращая нужную им конкретную реализацию продукта.
  4. Остальной код базового класса (алгоритм, использующий продукт) остаётся общим и не дублируется.

Ключевая идея: создание объекта откладывается до подкласса, но сам алгоритм использования этого объекта остаётся в одном месте.

Структура

  • Product - интерфейс создаваемого объекта.
  • ConcreteProduct - конкретные реализации продукта.
  • Creator - абстрактный класс с фабричным методом CreateProduct() и общей бизнес-логикой, которая этот метод использует.
  • ConcreteCreator - переопределяет фабричный метод, возвращая конкретный продукт.

Когда применять

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

Плюсы

  • Убирает дублирование общей логики между вариантами создателя.
  • Соблюдает принцип открытости/закрытости: новый вариант продукта добавляется новым подклассом, без изменения существующего кода.

Минусы

  • Требует создавать подкласс Creator на каждый новый вид продукта - если вариантов много и они добавляются часто, иерархия классов быстро разрастается.
  • Для простых случаев (когда общей логики вокруг создания почти нет) это избыточная абстракция - обычная функция-фабрика или Func<T> решают задачу проще.

Отличие от Abstract Factory

Factory Method - это один метод, переопределяемый в подклассе, для создания одного вида продукта. Abstract Factory - это отдельный объект-фабрика с несколькими методами, создающий семейство согласованных продуктов. Abstract Factory нередко реализуют, используя внутри несколько Factory Method.

Пример в .NET Framework / BCL

  • Stream.CreateWaitHandle() (устаревший, но исторически показательный пример).
  • Многие *Provider/*Factory классы в ADO.NET: DbProviderFactory.CreateConnection(), CreateCommand() - конкретная фабрика (SqlClientFactory, NpgsqlFactory) решает, объекты каких конкретных классов вернуть.
  • HttpMessageHandlerFactory внутри IHttpClientFactory.

Пример реализации на C#

FactoryMethod.cs
using System;

namespace DesignPatterns.Creational.FactoryMethod
{
    // Продукт: общий интерфейс для всех уведомлений
    public interface INotification
    {
        void Send(string message);
    }

    public sealed class EmailNotification : INotification
    {
        public void Send(string message) => Console.WriteLine($"[Email] {message}");
    }

    public sealed class SmsNotification : INotification
    {
        public void Send(string message) => Console.WriteLine($"[SMS] {message}");
    }

    // Создатель: базовый класс задаёт алгоритм работы (Notify),
    // но само создание продукта делегирует подклассам через CreateNotification.
    public abstract class NotificationSender
    {
        // Это и есть Factory Method - точка расширения для подклассов.
        protected abstract INotification CreateNotification();

        // Остальная логика общая для всех отправителей и переиспользуется без дублирования.
        public void Notify(string message)
        {
            INotification notification = CreateNotification();
            Console.WriteLine("Готовим отправку...");
            notification.Send(message);
        }
    }

    public sealed class EmailNotificationSender : NotificationSender
    {
        protected override INotification CreateNotification() => new EmailNotification();
    }

    public sealed class SmsNotificationSender : NotificationSender
    {
        protected override INotification CreateNotification() => new SmsNotification();
    }

    public static class Demo
    {
        public static void Run()
        {
            NotificationSender sender = new EmailNotificationSender();
            sender.Notify("Заказ подтверждён");

            sender = new SmsNotificationSender();
            sender.Notify("Ваш код: 4821");
        }
    }
}

Открыть FactoryMethod.cs отдельно Скачать FactoryMethod.cs