Как реализуется паттерн проектирования ‘Фабрика’ в Python?

Ответ

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

В Python существует несколько вариаций этого паттерна.

1. Простая Фабрика (Simple Factory)

Это не канонический паттерн из банды четырех (GoF), а скорее популярная идиома. Один класс или функция решает, какой объект создать, на основе переданного параметра.

Применение: Когда логика создания объектов несложная и не требует расширения через наследование.

class Dog:
    def speak(self): return "Woof!"

class Cat:
    def speak(self): return "Meow!"

# Простая фабрика в виде функции
def get_pet(pet_type: str):
    """Создает объект животного по его типу."""
    if pet_type == "dog":
        return Dog()
    elif pet_type == "cat":
        return Cat()
    raise ValueError("Unknown pet type")

# Использование
my_dog = get_pet("dog")
print(my_dog.speak()) # -> Woof!

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

Это канонический паттерн. Он определяет абстрактный метод для создания объекта (factory_method), а конкретные реализации этого метода находятся в подклассах. Таким образом, создание объекта делегируется дочерним классам.

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

from abc import ABC, abstractmethod

# Абстрактный продукт
class Animal(ABC):
    @abstractmethod
    def speak(self): pass

# Конкретные продукты
class Dog(Animal):
    def speak(self): return "Woof!"

class Cat(Animal):
    def speak(self): return "Meow!"

# Абстрактный создатель (фабрика)
class AnimalFactory(ABC):
    @abstractmethod
    def create_animal(self) -> Animal:
        pass

# Конкретные создатели
class DogFactory(AnimalFactory):
    def create_animal(self) -> Animal:
        return Dog()

class CatFactory(AnimalFactory):
    def create_animal(self) -> Animal:
        return Cat()

# Использование
def get_animal_from_factory(factory: AnimalFactory):
    animal = factory.create_animal()
    print(f"Created animal says: {animal.speak()}")

get_animal_from_factory(DogFactory()) # -> Created animal says: Woof!
get_animal_from_factory(CatFactory()) # -> Created animal says: Meow!

3. Абстрактная фабрика (Abstract Factory)

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

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

# Пример: Фабрика для создания GUI элементов под разные ОС

# Абстрактные продукты
class Button(ABC):
    @abstractmethod
    def render(self): pass

class Checkbox(ABC):
    @abstractmethod
    def render(self): pass

# Абстрактная фабрика
class GUIFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button: pass

    @abstractmethod
    def create_checkbox(self) -> Checkbox: pass

# Конкретная фабрика для Windows
class WinFactory(GUIFactory):
    def create_button(self) -> Button: return WinButton()
    def create_checkbox(self) -> Checkbox: return WinCheckbox()

# Конкретная фабрика для MacOS
class MacFactory(GUIFactory):
    def create_button(self) -> Button: return MacButton()
    def create_checkbox(self) -> Checkbox: return MacCheckbox()

# ... реализации конкретных продуктов WinButton, MacButton и т.д. ...

Основные преимущества паттерна:

  • Инкапсуляция: Скрывает логику создания объектов от клиентского кода.
  • Гибкость: Упрощает добавление новых типов продуктов, не изменяя существующий клиентский код.
  • Соблюдение принципов SOLID: Соответствует принципу открытости/закрытости (Open/Closed Principle) и принципу единственной ответственности (Single Responsibility Principle).

Ответ 18+ 🔞

А, слушай, смотри, вот эта вся ваша «Фабрика» — это, блядь, такая штука, которая как будто бы говорит: «Не парься, дружок, какой объект тебе слепить, я сама разберусь, ты только скажи, что тебе надо». Ну, типа, инкапсуляция создания, ебать её в сраку. Чтобы потом не бегать по коду и не менять каждый new Cat() на new Dog(), если начальство вдруг решит, что теперь у нас только попугаи.

Вот, например, Простая Фабрика. Это как самый тупой и честный способ. Есть у тебя функция, и ты ей кричишь: «Давай собаку!». А она тебе: «На, собаку». И всё. Пиздец просто.

def get_pet(pet_type: str):
    if pet_type == "dog":
        return Dog()
    elif pet_type == "cat":
        return Cat()
    raise ValueError("Unknown pet type")

Вот и вся магия. Никаких тебе подвохов. Но если завтра попросит хомяка — придётся лезть в эту функцию и дописывать. Что, в принципе, не конец света, если ты не планируешь разводить тут зоопарк на овердохуища видов.

Дальше идёт Фабричный метод. Это уже посерьёзнее, блядь. Тут уже начинается высший пилотаж. Создаёшь абстрактного создателя, который говорит: «У меня есть метод create_animal(), но какого именно — это пусть мои дети решают». И вот ты делаешь DogFactory, а он рожает собак, и CatFactory, который, внезапно, кошек. Клиентский код при этом нихуя не знает, что там внутри творится, он просто пользуется фабрикой.

def get_animal_from_factory(factory: AnimalFactory):
    animal = factory.create_animal()
    print(f"Created animal says: {animal.speak()}")

Красота, да? Хочешь сменить всю фауну в приложении — просто подсовываешь другую фабрику. Ни одной строчки клиентского кода не трогал, а он уже мычит вместо того, чтобы лаять. Ёпта, вот это поворот.

Ну и апогей всего этого цирка — Абстрактная фабрика. Это когда тебе нужно не просто одно животное, а целый, блядь, комплект связанных объектов. Типа, сделай мне интерфейс в стиле Windows: и кнопку, и чекбокс, и всё такое, чтоб в одном дизайне было. А потом — бац! — и всё в стиле macOS. И чтобы не пришлось вручную двадцать классов менять.

class WinFactory(GUIFactory):
    def create_button(self) -> Button: return WinButton()
    def create_checkbox(self) -> Checkbox: return WinCheckbox()

Подсунул WinFactory — получил виндовые контролы. Подсунул MacFactory — получил маковские. Клиентский код опять же нихуя не парится, он просто работает с абстрактными Button и Checkbox. А что там под капотом рисуется — квадратное или скруглённое — ему похуй.

В чём, собственно, соль, блядь?

  • Спрятали грязь: Вся возня с new и условиями теперь не в основном коде, а в отдельных, специально обученных местах. Чистота, порядок, ебать мои старые костыли.
  • Гибкость охуенная: Захотел новую зверюшку или новый стиль интерфейса — написал новый класс-фабрику и подменил её в одном месте. Всё остальное работает как часы.
  • SOLID как бы не против: Принципы эти ваши, вроде «открыт для расширения, закрыт для изменений», тут вроде как соблюдаются. Ну, то есть, не нарушаешь ты их в открытую, а так, аккуратно обходишь.

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