Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

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

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Builder и Fluent Interface
builder

Builder и Fluent Interface

Построение сложных объектов пошагово. Fluent Interface через метод __enter__ и контекстные менеджеры. Dataclasses и attrs для Builder.

Builder и Fluent Interface

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


#Builder: пошаговое создание сложных объектов

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

#Проблема: телескопический конструктор

# ❌ ПЛОХО: Конструктор с кучей параметров class Query: def __init__(self, table, columns=None, where=None, order_by=None, limit=None, offset=None, join=None, group_by=None, having=None, distinct=False): self.table = table self.columns = columns or '*' self.where = where # ... ещё 10 параметров ... # Вызов выглядит ужасно: query = Query( table="users", columns=["id", "name"], where="age > 18", order_by="name", limit=10, offset=0, distinct=True )

#Решение 1: Builder с методами

# ✅ ХОРОШО: Builder для пошагового построения class Query: def __init__(self): self._table = None self._columns = '*' self._where = None self._order_by = None self._limit = None self._offset = None self._distinct = False def table(self, name: str) -> "Query": self._table = name return self def select(self, *columns: str) -> "Query": self._columns = columns if columns else '*' return self def where(self, condition: str) -> "Query": self._where = condition return self def order_by(self, column: str) -> "Query": self._order_by = column return self def limit(self, n: int) -> "Query": self._limit = n return self def offset(self, n: int) -> "Query": self._offset = n return self def distinct(self) -> "Query": self._distinct = True return self def build(self) -> str: parts = [f"SELECT {'DISTINCT ' if self._distinct else ''}{self._columns}"] parts.append(f"FROM {self._table}") if self._where: parts.append(f"WHERE {self._where}") if self._order_by: parts.append(f"ORDER BY {self._order_by}") if self._limit: parts.append(f"LIMIT {self._limit}") if self._offset: parts.append(f"OFFSET {self._offset}") return " ".join(parts) # Использование query = (Query() .table("users") .select("id", "name") .where("age > 18") .order_by("name") .limit(10) .build()) print(query) # SELECT id, name FROM users WHERE age > 18 ORDER BY name LIMIT 10

#Fluent Interface: цепочки вызовов

Fluent Interface — стиль проектирования API, где методы возвращают self, позволяя цепочечные вызовы.

#Принципы Fluent Interface

  1. Методы возвращают self для цепочек
  2. Читаемость как предложение — builder.select().from_().where()
  3. Контекст сохраняется между вызовами

#Пример: Email Builder

class Email: def __init__(self): self._from = None self._to = [] self._cc = [] self._subject = None self._body = None self._attachments = [] def from_(self, email: str) -> "Email": self._from = email return self def to(self, *emails: str) -> "Email": self._to.extend(emails) return self def cc(self, *emails: str) -> "Email": self._cc.extend(emails) return self def subject(self, subject: str) -> "Email": self._subject = subject return self def body(self, text: str) -> "Email": self._body = text return self def attach(self, *files: str) -> "Email": self._attachments.extend(files) return self def build(self) -> dict: return { "from": self._from, "to": self._to, "cc": self._cc, "subject": self._subject, "body": self._body, "attachments": self._attachments, } # Использование — читается как предложение! email = (Email() .from_("noreply@example.com") .to("user@example.com", "admin@example.com") .cc("boss@example.com") .subject("Welcome!") .body("Hello and welcome!") .attach("guide.pdf", "terms.pdf") .build())

#Builder с валидацией

from dataclasses import dataclass, field from typing import Optional, List @dataclass class HTTPRequest: url: str method: str = "GET" headers: dict = field(default_factory=dict) body: Optional[str] = None timeout: int = 30 class RequestBuilder: VALID_METHODS = {"GET", "POST", "PUT", "DELETE", "PATCH"} def __init__(self): self._url = None self._method = "GET" self._headers = {} self._body = None self._timeout = 30 def url(self, url: str) -> "RequestBuilder": if not url.startswith(("http://", "https://")): raise ValueError("URL must start with http:// or https://") self._url = url return self def method(self, method: str) -> "RequestBuilder": method = method.upper() if method not in self.VALID_METHODS: raise ValueError(f"Invalid method: {method}") self._method = method return self def header(self, key: str, value: str) -> "RequestBuilder": self._headers[key] = value return self def body(self, body: str) -> "RequestBuilder": if self._method == "GET": raise ValueError("GET request cannot have a body") self._body = body return self def timeout(self, seconds: int) -> "RequestBuilder": if seconds <= 0: raise ValueError("Timeout must be positive") self._timeout = seconds return self def build(self) -> HTTPRequest: if not self._url: raise ValueError("URL is required") return HTTPRequest( url=self._url, method=self._method, headers=self._headers, body=self._body, timeout=self._timeout, ) # Использование request = (RequestBuilder() .url("https://api.example.com/users") .method("POST") .header("Content-Type", "application/json") .header("Authorization", "Bearer token") .body('{"name": "Alice"}') .timeout(60) .build())

#Builder через contextmanager

from contextlib import contextmanager class TransactionBuilder: def __init__(self, db): self.db = db self._operations = [] def insert(self, table: str, **data): self._operations.append(("insert", table, data)) return self def update(self, table: str, where: str, **data): self._operations.append(("update", table, where, data)) return self def delete(self, table: str, where: str): self._operations.append(("delete", table, where)) return self def execute(self): with self.db.transaction(): for op in self._operations: if op[0] == "insert": self.db.insert(op[1], **op[2]) elif op[0] == "update": self.db.update(op[1], op[2], **op[3]) elif op[0] == "delete": self.db.delete(op[1], op[2]) @contextmanager def transaction_builder(db): """Контекстный менеджер для builder""" builder = TransactionBuilder(db) try: yield builder builder.execute() except Exception: # Автоматический rollback при ошибке db.rollback() raise # Использование with transaction_builder(db) as tx: tx.insert("users", name="Alice", email="alice@example.com") \ .update("accounts", "user_id=1", balance=100) \ .delete("sessions", "expired=true") # Все операции выполняются в транзакции

#Builder для сложных структур

#HTML Builder

class HTMLBuilder: def __init__(self, tag: str): self.tag = tag self.attrs = {} self.children = [] self.text = None def attr(self, name: str, value: str) -> "HTMLBuilder": self.attrs[name] = value return self def add(self, child: "HTMLBuilder") -> "HTMLBuilder": self.children.append(child) return self def text_(self, text: str) -> "HTMLBuilder": self.text = text return self def build(self) -> str: attrs_str = " ".join(f'{k}="{v}"' for k, v in self.attrs.items()) opening = f"<{self.tag} {attrs_str}>" if attrs_str else f"<{self.tag}>" if self.text: return f"{opening}{self.text}</{self.tag}>" if not self.children: return f"{opening}</{self.tag}>" children_html = "".join(child.build() for child in self.children) return f"{opening}{children_html}</{self.tag}>" # DSL для HTML def div() -> HTMLBuilder: return HTMLBuilder("div") def a(href: str = None) -> HTMLBuilder: builder = HTMLBuilder("a") if href: builder.attr("href", href) return builder def span() -> HTMLBuilder: return HTMLBuilder("span") # Использование html = (div() .attr("class", "container") .add( div().attr("class", "header") .add(span().text_("Welcome!")) ) .add( a(href="/login").text_("Login") ) .build()) print(html) # <div class="container"><div class="header"><span>Welcome!</span></div><a href="/login">Login</a></div>

#Builder через dataclasses

from dataclasses import dataclass, field from typing import Optional, List @dataclass class Pizza: size: str = "M" dough: str = "thin" sauce: Optional[str] = None toppings: List[str] = field(default_factory=list) cheese: bool = True class PizzaBuilder: def __init__(self): self._pizza = Pizza() def size(self, size: str) -> "PizzaBuilder": self._pizza.size = size return self def dough(self, dough: str) -> "PizzaBuilder": self._pizza.dough = dough return self def sauce(self, sauce: str) -> "PizzaBuilder": self._pizza.sauce = sauce return self def topping(self, topping: str) -> "PizzaBuilder": self._pizza.toppings.append(topping) return self def no_cheese(self) -> "PizzaBuilder": self._pizza.cheese = False return self def build(self) -> Pizza: return self._pizza # Использование pizza = (PizzaBuilder() .size("L") .dough("thick") .sauce("tomato") .topping("mozzarella") .topping("basil") .topping("pepperoni") .build()) print(pizza) # Pizza(size='L', dough='thick', sauce='tomato', # toppings=['mozzarella', 'basil', 'pepperoni'], cheese=True)

#Когда НЕ использовать Builder

#Избыточная сложность

# ❌ ПЛОХО: Builder для простого объекта class UserBuilder: def __init__(self): self._name = None self._email = None def name(self, name: str): self._name = name return self def email(self, email: str): self._email = email return self def build(self): return User(self._name, self._email) # Зачем, если можно: @dataclass class User: name: str email: str user = User(name="Alice", email="alice@example.com")

Правило: Используйте Builder, когда:

  1. Объект имеет много параметров (более 4-5)
  2. Некоторые параметры опциональны
  3. Нужна валидация в процессе построения
  4. Требуется Fluent Interface для читаемости

#Что дальше?

Изучите тему Prototype и копирование объектов для создания объектов через копирование.

Далее: Prototype и копирование объектов