Menu
Coddy logo textTech

Паттерн Factory

Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 55 из 61.

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

Этот паттерн особенно эффективен в сочетании с полиморфизмом. Фабрика может возвращать различные конкретные типы через общий базовый указатель, скрывая детали создания от вызывающей стороны. Вызывающая сторона просто запрашивает «фигуру», а фабрика решает, создать ли круг, прямоугольник или что-то другое.

Вот типичная реализация фабрики:

typedef enum {
    SHAPE_CIRCLE,
    SHAPE_RECTANGLE
} ShapeType;

Shape* create_shape(ShapeType type) {
    switch (type) {
        case SHAPE_CIRCLE:
            return (Shape*)create_circle(5.0);
        case SHAPE_RECTANGLE:
            return (Shape*)create_rectangle(4.0, 3.0);
        default:
            return NULL;
    }
}

Фабрика принимает идентификатор (обычно перечисление enum) и использует оператор switch, чтобы определить, какой конструктор вызвать. Она возвращает Shape* независимо от фактически созданного типа. Вызывающей стороне не нужно знать о Circle или Rectangle — она просто работает с базовым типом:

Shape* s = create_shape(SHAPE_CIRCLE);
s->draw(s);  // полиморфный вызов
free_shape(s);

Этот паттерн упрощает добавление новых типов: достаточно добавить новое значение enum и новый case в фабрику. Остальная часть вашего кода останется неизменной.

challenge icon

Задание

Легко

Давайте создадим Pet Factory (Фабрику питомцев) — систему, которая создает различные типы питомцев через одну функцию-фабрику. Вместо того чтобы распределять логику создания по всему коду, вы централизуете ее в одном месте, которое решает, какой тип питомца создать на основе входного идентификатора.

Вы организуете свой код в трех файлах:

  • pet.h: Определите перечисление PetType со значениями PET_DOG, PET_CAT и PET_BIRD. Создайте базовую структуру Pet, которая содержит указатель на функцию speak (которая принимает const Pet* и ничего не возвращает). Объявите функцию-фабрику create_pet, которая принимает PetType и возвращает Pet*, а также функцию free_pet для очистки памяти.
  • pet.c: Определите три конкретные структуры питомцев — Dog, Cat и Bird — каждая из которых содержит Pet в качестве первого члена. Реализуйте функцию speak для каждого типа:
    • Собаки выводят: Woof!
    • Кошки выводят: Meow!
    • Птицы выводят: Tweet!
    Создайте функции-конструкторы для каждого типа питомца, которые выделяют память и привязывают соответствующую функцию speak. Затем реализуйте фабрику create_pet — используйте оператор switch по PetType, чтобы вызвать нужный конструктор и вернуть результат в виде Pet*. Для недопустимых типов возвращайте NULL. Реализуйте free_pet для освобождения выделенной памяти.
  • main.c: Считайте целое число, представляющее тип питомца (0 для собаки, 1 для кошки, 2 для птицы). Используйте свою фабрику для создания соответствующего питомца, вызовите его функцию speak полиморфно через указатель на базовый тип Pet*, а затем освободите память питомца.

Ваша программа получит один входной параметр: целое число, указывающее, какого питомца создать.

Пример вывода, когда на входе 0:

Woof!

Пример вывода, когда на входе 1:

Meow!

Пример вывода, когда на входе 2:

Tweet!

Прелесть паттерна «Фабрика» заключается в том, что main.c не нужно знать о структурах Dog, Cat или Bird — он работает только с базовым типом Pet. Добавление нового типа питомца в будущем означает добавление нового значения в перечисление и нового варианта в фабрике без изменения кода, который использует питомцев.

Шпаргалка

Паттерн «Фабрика» (Factory pattern) централизует создание объектов в одной функции, скрывая детали создания от вызывающей стороны.

Фабричная функция принимает идентификатор (обычно перечисление enum) и использует оператор switch для определения того, какой конструктор вызвать, возвращая указатель на базовый тип:

typedef enum {
    SHAPE_CIRCLE,
    SHAPE_RECTANGLE
} ShapeType;

Shape* create_shape(ShapeType type) {
    switch (type) {
        case SHAPE_CIRCLE:
            return (Shape*)create_circle(5.0);
        case SHAPE_RECTANGLE:
            return (Shape*)create_rectangle(4.0, 3.0);
        default:
            return NULL;
    }
}

Вызывающая сторона работает только с базовым типом, что обеспечивает полиморфное поведение:

Shape* s = create_shape(SHAPE_CIRCLE);
s->draw(s);  // polymorphic call
free_shape(s);

Преимущества: Добавление новых типов требует лишь добавления нового значения enum и варианта switch, без изменения существующего кода, использующего фабрику.

Попробуйте сами

#include <stdio.h>
#include "pet.h"

int main(void) {
    int type;
    scanf("%d", &type);
    
    // TODO: Используйте фабрику для создания соответствующего питомца
    // Hint: Приведите int к PetType
    
    // TODO: Вызовите функцию speak полиморфно через указатель Pet*
    
    // TODO: Освободите питомца
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming