суббота, 19 июля 2025 г.

Factory Method, Giga, Path

Factory Method, Giga, Path

https://refactoring.guru/design-patterns/factory-method

https://giga.chat/link/gcsUUyXJCB

D:\VC25\Otus\Py\projects\des_patterns\factory_method

Паттерн «Фабричный метод»

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

1. Суть паттерна

Паттерн «Фабричный метод» — это порождающий шаблон проектирования,

предназначенный для создания объектов через косвенное обращение к фабричному методу,

который определяет интерфейс для создания экземпляров производных классов.

Такой подход позволяет наследникам самим решать, какой конкретно экземпляр создать.

Основная цель паттерна — перенос ответственности за создание объектов из

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

2. Проблемы, решаемые паттерном

Проблема:

допустим, у вас есть иерархия классов, и вам нужно создать объекты, принадлежащие разным классам,

исходя из контекста использования.

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

Решение:

вместо того чтобы клиентская логика напрямую создавала объекты нужного типа,

вы выносите создание объектов в отдельный метод (фабрику),

который переопределяется в подклассах.

Это предотвращает сильное сцепление клиентской логики с конкретными классами и позволяет спокойно расширять

иерархию классов без изменения клиентского кода.

3. Применение паттерна

Сценарий 1: Работа с графикой

Допустим, у вас есть приложение, работающее с фигурами (прямоугольники, окружности, линии и т.д.).

Вам нужно рисовать фигуры разного типа в зависимости от заданных параметров.

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

Решение с использованием фабрики:

Вы создаете базовый класс с фабрикой, определяющей интерфейс для создания фигуры,

а подклассы ответственны за создание определенных типов фигур.

from abc import ABC, abstractmethod

# Базовая фигура
class Shape(ABC):
    @abstractmethod
    def draw(self):
        pass

# Производные фигуры
class Rectangle(Shape):
    def draw(self):
        print("Рисование прямоугольника")

class Circle(Shape):
    def draw(self):
        print("Рисование круга")

# Базовый класс с фабричным методом
class ShapeFactory(ABC):
    @abstractmethod
    def create_shape(self):
        pass

# Подкласс, ответственный за создание прямоугольников
class RectangleFactory(ShapeFactory):
    def create_shape(self):
        return Rectangle()

# Подкласс, ответственный за создание кругов
class CircleFactory(ShapeFactory):
    def create_shape(self):
        return Circle()

# Клиентский код
def main():
    rectangle_factory = RectangleFactory()
    circle_factory = CircleFactory()

    shapes = [
        rectangle_factory.create_shape(),
        circle_factory.create_shape()
    ]

    for shape in shapes:
        shape.draw()

if __name__ == "__main__":
    main()
Результат выполнения:
Рисование прямоугольника
Рисование круга
Сценарий 2: Форматы файлов

Допустим, ваше приложение сохраняет данные в разных форматах (JSON, XML, CSV и др.)

и вам нужно уметь правильно сериализовывать объекты, основываясь на выбранном формате.

Решение с использованием фабрики:

Вы создаете базовую структуру формата с фабрикой, которая создает нужные форматы файлов,

а подклассы (форматы) отвечают за реализацию сериализации в конкретном формате.

from abc import ABC, abstractmethod

# Базовый класс формата
class Format(ABC):
    @abstractmethod
    def serialize(self, data):
        pass

# Реализации форматов
class JSONFormat(Format):
    def serialize(self, data):
        print(f"Сериализация в JSON: {data}")

class XMLFormat(Format):
    def serialize(self, data):
        print(f"Сериализация в XML: {data}")

# Фабрика форматов
class FormatFactory(ABC):
    @abstractmethod
    def create_format(self):
        pass

# Подклассы фабрик
class JSONFactory(FormatFactory):
    def create_format(self):
        return JSONFormat()

class XMLFactory(FormatFactory):
    def create_format(self):
        return XMLFormat()

# Клиентский код
def save_data(factory, data):
    format_obj = factory.create_format()
    format_obj.serialize(data)

save_data(JSONFactory(), {"key": "value"})
save_data(XMLFactory(), "<root><item>value</item></root>")
Результат выполнения:
Сериализация в JSON: {'key': 'value'}
Сериализация в XML: <root><item>value</item></root>

4. Особенности паттерна

  • Интерфейс:
  • Метод создания объекта объявлен в базовом классе, но реализация предоставляется подклассами.
  • Наследственность:
  • Важно, чтобы базовый класс знал, какого типа объекты создаются (через абстрактный метод),
  • но не имел понятия о фактическом подклассе.
  • Расширяемость:
  • Можно легко добавить новые типы объектов, создавая новые подклассы фабрики.

5. Преимущества паттерна

  • Открытая-закрытая архитектура: Новый функционал добавляется добавлением новых подклассов, не изменяя базовый класс.
  • Слабое сцепление: Низкая зависимость между клиентами и конкретными типами объектов.
  • Четкое разделение ответственности: Родительский класс отвечает за интерфейс, а подклассы — за реализацию.

6. Недостатки паттерна

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

7. Альтернативы

Альтернатива паттерну «Фабричный метод» — паттерн «Абстрактная фабрика», который создает целые семейства связанных объектов,

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


Следующим будем рассматривать следующий паттерн из списка.

То есть мы фабричным методом создаем обьект не привязанный к конкретному типу

Правильно понимаю. Зачем нам нужна отвязка от конкретного типа ?

Ответ

Верно понимаете!

Фабричный метод позволяет создавать объекты,

не привязываясь к конкретному типу, и эта особенность несет несколько важных преимуществ:

Зачем нужна отвязка от конкретного типа?

  1. Открытость для расширения:
    • Ваш клиентский код становится открытым для будущих изменений.
    • Допустим, завтра появится новая версия объекта или вообще новый тип объекта.
    • Тогда, добавив всего один новый подкласс фабрики,
    • вы получаете возможность создавать объекты нового типа без изменения самого клиентского кода.
  2. Легкость поддержки и изменения:
    • Менее связанный код проще поддерживать и развивать.
    • Вы можете сосредоточиться на изменении или добавлении конкретных видов объектов в отдельном месте (в подклассах фабрики),
    • не трогая основной код приложения.
  3. Инкапсуляция логики создания:
    • Детали создания объекта скрыты внутри фабрики. Клиентскому коду неважно, как именно создается объект,
    • и это защищает вашу программу от ненужных подробностей.
  4. Увеличение гибкости:
    • Вместо жесткой фиксации на одном конкретном типе объекта, вы даете возможность подклассам влиять на создание нужных объектов,
    • что делает код более гибким и адаптируемым к будущим потребностям.
  5. Приспособляемость к агрегациям:
    • Если объект состоит из множества мелких компонентов, фабрика может собрать объект целиком,
    • сократив усилия по ручной сборке объекта в клиентском коде.

Пример реальной пользы в Python

Допустим, у вас есть библиотека для работы с изображениями, которая должна поддерживать разные форматы (JPEG, PNG, GIF и т.д.).

Ваш код без фабрики выглядел бы примерно так:

def process_image(filename):
    extension = filename.split('.')[-1].lower()
    if extension == 'jpg':
        image = JPGImage(filename)
    elif extension == 'png':
        image = PNGImage(filename)
    else:
        raise ValueError("Unsupported image type")
    return image

Но это плохо масштабируется. Если завтра появится SVG или WebP, придется редактировать функцию process_image.

С использованием фабрики:

from abc import ABC, abstractmethod

class Image(ABC):
    @abstractmethod
    def load(self):
        pass

class JPGImage(Image):
    def load(self):
        print("Загрузили JPEG изображение")

class PNGImage(Image):
    def load(self):
        print("Загрузили PNG изображение")

class ImageFactory(ABC):
    @abstractmethod
    def create_image(self, filename):
        pass

class JPGFactory(ImageFactory):
    def create_image(self, filename):
        return JPGImage(filename)

class PNGFactory(ImageFactory):
    def create_image(self, filename):
        return PNGImage(filename)

def process_image(filename, factory):
    image = factory.create_image(filename)
    image.load()

factory_jpg = JPGFactory()
factory_png = PNGFactory()

process_image("photo.jpg", factory_jpg)  # Загрузили JPEG изображение
process_image("graph.png", factory_png)  # Загрузили PNG изображение

Теперь вы добавили слой абстракции и сделали код готовым к новому формату,

без изменения клиентского кода process_image.

Просто добавьте новый подкласс фабрики и получите нужный объект.

Итог:

Фабричный метод позволяет вашим программам оставаться открытыми для будущего развития,

гибкими и легко поддерживаемыми, а главное — отвязанными от конкретных типов объектов,

что положительно сказывается на качестве и долговечности вашего кода.

Комментариев нет:

Отправить комментарий