Паттерн Строитель в Python

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

Ключевая идея

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


Зачем нам нужен паттерн Строитель?

Проблемы традиционных методов конструирования

Сначала рассмотрим пример, в котором не используется паттерн Строитель:

Пример

class Computer:
    def __init__(self, cpu, memory, storage, graphics_card, monitor):
        self.cpu = cpu
        self.memory = memory
        self.storage = storage
        self.graphics_card = graphics_card
        self.monitor = monitor
   
    def __str__(self):
        return f"Компьютер: CPU={self.cpu}, Память={self.memory}GB, Хранилище={self.storage}GB, Графика={self.graphics_card}, Монитор={self.monitor}"

# Создать объект компьютера
computer = Computer("Intel i7", 16, 512, "NVIDIA RTX 3060", "27-дюймовый 4K")
print(computer)

Недостатки этого подхода:

  • Слишком много параметров конструктора, что затрудняет сопровождение
  • Приходится запоминать порядок всех параметров
  • Процесс создания недостаточно гибок
  • Плохая читаемость кода

Компоненты паттерна «Строитель»

Паттерн «Строитель» включает четыре основные роли:

1. Продукт

Сложный объект, который нужно создать

2. Builder (Абстрактный строитель)

Определяет абстрактный интерфейс для создания различных частей продукта

3. ConcreteBuilder (Конкретный строитель)

Реализует интерфейс Builder, конструируя и собирая различные части

4. Директор

Создает объект, использующий интерфейс Builder


Полная реализация кода

Давайте полностью реализуем паттерн Builder на примере конфигурации компьютера:

1. Класс продукта

Пример

class Computer:
    """Класс продукта Computer"""
    def __init__(self):
        self.cpu = None
        self.memory = None
        self.storage = None
        self.graphics_card = None
        self.monitor = None
   
    def __str__(self):
        specs = []
        if self.cpu:
            specs.append(f"Процессор: {self.cpu}")
        if self.memory:
            specs.append(f"Память: {self.memory}GB")
        if self.storage:
            specs.append(f"Накопитель: {self.storage}GB")
        if self.graphics_card:
            specs.append(f"Видеокарта: {self.graphics_card}")
        if self.monitor:
            specs.append(f"Монитор: {self.monitor}")
       
        return "Конфигурация компьютера:\n" + "\n".join(f" - {spec}" for spec in specs)

2. Абстрактный строитель (Builder)

Пример

from abc import ABC, abstractmethod

class ComputerBuilder(ABC):
    """Абстрактный класс строителя компьютера"""
   
    def __init__(self):
        self.computer = Computer()
   
    @abstractmethod
    def build_cpu(self):
        pass
   
    @abstractmethod
    def build_memory(self):
        pass
   
    @abstractmethod
    def build_storage(self):
        pass
   
    @abstractmethod
    def build_graphics_card(self):
        pass
   
    @abstractmethod
    def build_monitor(self):
        pass
   
    def get_computer(self):
        return self.computer

3. ConcreteBuilder (Конкретный строитель)

Пример

class GamingComputerBuilder(ComputerBuilder):
    """Строитель игрового компьютера"""
   
    def build_cpu(self):
        self.computer.cpu = "Intel i9-13900K"
   
    def build_memory(self):
        self.computer.memory = 32
   
    def build_storage(self):
        self.computer.storage = 2000  # 2TB
   
    def build_graphics_card(self):
        self.computer.graphics_card = "NVIDIA RTX 4090"
   
    def build_monitor(self):
        self.computer.monitor = "32-inch 4K 144Hz"

class OfficeComputerBuilder(ComputerBuilder):
    """Строитель офисных компьютеров"""
   
    def build_cpu(self):
        self.computer.cpu = "Intel i5-13400"
   
    def build_memory(self):
        self.computer.memory = 16
   
    def build_storage(self):
        self.computer.storage = 512
   
    def build_graphics_card(self):
        self.computer.graphics_card = "Встроенная графика"
   
    def build_monitor(self):
        self.computer.monitor = "24-inch 1080P"

4. Директор

Пример

class ComputerDirector:
    """Директор по сборке компьютеров"""
   
    def __init__(self, builder):
        self.builder = builder
   
    def construct_computer(self):
        """Полный процесс сборки компьютера"""
        self.builder.build_cpu()
        self.builder.build_memory()
        self.builder.build_storage()
        self.builder.build_graphics_card()
        self.builder.build_monitor()
   
    def get_computer(self):
        return self.builder.get_computer()

Пример использования

Теперь давайте посмотрим, как использовать паттерн «Строитель» для создания различных типов компьютеров:

Пример

def main():
    print("=== Демонстрация паттерна Строитель ===\n\n")
   
    # Создание игрового компьютера
    print("1. Сборка игрового компьютера:")
    gaming_builder = GamingComputerBuilder()
    director = ComputerDirector(gaming_builder)
    director.construct_computer()
    gaming_computer = director.get_computer()
    print(gaming_computer)
   
    print("\n" + "="*50 + "\n")
   
    # Создание офисного компьютера
    print("2. Сборка офисного компьютера:")
    office_builder = OfficeComputerBuilder()
    director = ComputerDirector(office_builder)
    director.construct_computer()
    office_computer = director.get_computer()
    print(office_computer)
   
    print("\n" + "="*50 + "\n")
   
    # Создать пользовательский компьютер
    print("3. Сборка пользовательского компьютера:")
    custom_builder = CustomComputerBuilder()
    custom_builder.build_cpu("AMD Ryzen 7 7800X3D")
    custom_builder.build_memory(64)
    custom_builder.build_storage(4000)
    custom_builder.build_graphics_card("AMD RX 7900 XTX")
    custom_builder.build_monitor("34-inch изогнутый ультраширокий монитор")
    custom_computer = custom_builder.get_computer()
    print(custom_computer)

# Пользовательский строитель (сборка с необязательными шагами)
class CustomComputerBuilder:
    """Пользовательский строитель компьютера"""
   
    def __init__(self):
        self.computer = Computer()
   
    def build_cpu(self, cpu):
        self.computer.cpu = cpu
        return self  # Вернуть self для поддержки цепочек вызовов
   
    def build_memory(self, memory):
        self.computer.memory = memory
        return self
   
    def build_storage(self, storage):
        self.computer.storage = storage
        return self
   
    def build_graphics_card(self, graphics_card):
        self.computer.graphics_card = graphics_card
        return self
   
    def build_monitor(self, monitor):
        self.computer.monitor = monitor
        return self
   
    def get_computer(self):
        return self.computer

if __name__ == "__main__":
    main()

Варианты паттерна «Строитель»

1. Строитель с текучим интерфейсом

Пример

class Pizza:
    def __init__(self):
        self.size = None
        self.cheese = False
        self.pepperoni = False
        self.mushrooms = False
   
    def __str__(self):
        ingredients = []
        if self.cheese: ingredients.append("Cheese")
        if self.pepperoni: ingredients.append("Italian sausage")
        if self.mushrooms: ingredients.append("Грибы")
        return f"{self.size}-inch пицца, начинки: {', '.join(ingredients)}"

class PizzaBuilder:
    def __init__(self, size):
        self.pizza = Pizza()
        self.pizza.size = size
   
    def add_cheese(self):
        self.pizza.cheese = True
        return self
   
    def add_pepperoni(self):
        self.pizza.pepperoni = True
        return self
   
    def add_mushrooms(self):
        self.pizza.mushrooms = True
        return self
   
    def build(self):
        return self.pizza

# Используйте fluent-интерфейс
pizza = (PizzaBuilder(12)
         .add_cheese()
         .add_pepperoni()
         .add_mushrooms()
         .build())
print(pizza)

Плюсы и минусы паттерна «Строитель»

Преимущества

Преимущества Описание
Хорошая инкапсуляция Процесс конструирования отделён от представления; клиентам не нужно знать внутренние детали.
Высокая расширяемость Новые конкретные строители можно удобно добавлять.
Лучший контроль Позволяет более тонко управлять процессом конструирования.
Читаемость кода Код становится понятнее при использовании цепочечных вызовов.

Недостатки

Недостатки Описание
Добавляет сложность Требует определения нескольких классов, что увеличивает сложность системы.
Не подходит, когда продукты сильно различаются Если продукты сильно различаются между собой, паттерн «Строитель» подходит не очень хорошо.
Строители зависят от продукта Строителю нужно знать конкретные детали продукта.

Применимые сценарии

Случаи, подходящие для паттерна «Строитель»:

  1. Создание сложных объектов: нужно создавать сложные объекты, состоящие из нескольких частей.
  2. Стабильный процесс конструирования: процесс конструирования относительно стабилен, но компоненты объекта часто меняются.
  3. Множественные представления: создаваемый объект имеет несколько различных представлений.
  4. Избегайте перекрывающихся конструкторов: избегайте использования конструкторов с большим количеством параметров.

Примеры практического применения:

  • Конвертер документов: преобразование документов в различные форматы, такие как PDF, Word, HTML.
  • Приготовление еды: приготовление блюд для разных наборов (комбинаций бургеров, картофеля фри, напитков).
  • Создание компонентов интерфейса: построение сложных компонентов пользовательского интерфейса.
  • Генерация отчётов: генерация отчётов, содержащих различные части (заголовок, данные, диаграммы).

Практические упражнения

Упражнение 1: Реализуйте строитель автомобиля

Попробуйте реализовать паттерн «строитель» для автомобиля со следующими компонентами:

  • Двигатель (бензиновый, электрический, гибридный)
  • Цвет кузова (красный, синий, чёрный, белый)
  • Размер колёс (16 дюймов, 18 дюймов, 20 дюймов)
  • Интерьер (кожа, ткань)

Упражнение 2: Улучшите конструктор компьютера

Добавьте следующие возможности в конструктор компьютера:

  • Дополнительные компоненты (например, устанавливать ли оптический привод)
  • Логика проверки (например, объём памяти не может быть меньше 4GB)
  • Функция расчёта цены

Итог

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

Помните основную идею паттернов проектирования:Не используйте паттерны ради использования паттернов, а для решения конкретных задач проектирования.В реальной разработке решайте, использовать ли паттерн Строитель, исходя из конкретных требований.

Другие расширения