Padrões Factory & Builder
Factories centralizam a criação de objetos por trás de uma função ou método de classe, de modo que os chamadores permaneçam desacoplados de tipos concretos. Builders montam objetos complexos passo a passo quando construtores exigiriam muitos parâmetros ou combinações opcionais.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
from dataclasses import dataclass
@dataclass
class HttpClient:
base_url: str
timeout: float = 30.0
retries: int = 3
def http_client_from_env(env: dict) -> HttpClient:
return HttpClient(
base_url=env["API_URL"],
timeout=float(env.get("HTTP_TIMEOUT", 30)),
)Quando usar isso:
- Factory quando a lógica de criação depende de configuração, ambiente ou tipo em tempo de execução
- Builder quando objetos têm muitos campos opcionais ou validação entre etapas
- Construtores de classmethod (
from_dict,from_url) como factories leves - Ocultando ciclos de importação - o módulo factory importa classes concretas, os chamadores não
- Test doubles - factories aceitam backends injetáveis
Exemplo de Trabalho
Uma factory seleciona backends de armazenamento; um builder constrói uma configuração de relatório validada.
from dataclasses import dataclass, field
from typing import Protocol
class Storage(Protocol):
def save(self, key: str, data: bytes) -> None: ...
@dataclass
class MemoryStorage:
_data: dict[str, bytes] = field(default_factory=dict)
def save(self, key: str, data: bytes) -> None:
self._data[key] = data
@dataclass
class FileStorage:
root: str
def save(self, key: str, data: bytes) -> None:
print(f"escreve {key} em {self.root}")
def storage_factory(kind: str, **kwargs) -> Storage:
if kind == "memory":
return MemoryStorage()
if kind == "file":
return FileStorage(root=kwargs["root"])
raise ValueError(f"armazenamento desconhecido: {kind}")
@dataclass
class ReportConfig:
title: str
columns: list[str]
page_size: int = 50
class ReportConfigBuilder:
def __init__(self, title: str) -> None:
self._title = title
self._columns: list[str] = []
self._page_size = 50
def add_column(self, name: str) -> "ReportConfigBuilder":
self._columns.append(name)
return self
def page_size(self, size: int) -> "ReportConfigBuilder":
if size < 1:
raise ValueError("page_size deve ser positivo")
self._page_size = size
return self
def build(self) -> ReportConfig:
if not self._columns:
raise ValueError("pelo menos uma coluna é necessária")
return ReportConfig(self._title, list(self._columns), self._page_size)
store = storage_factory("memory")
store.save("report.pdf", b"%PDF")
cfg = (
ReportConfigBuilder("Vendas")
.add_column("sku")
.add_column("receita")
.page_size(100)
.build()
)
print(cfg)O que isso demonstra:
- Factory encapsula a construção ramificada
- Tipos de Protocolo mantêm os chamadores independentes do armazenamento
- Métodos Builder retornam
selfpara encadeamento fluente build()valida invariantes antes de retornar o produto
Mergulho Profundo
Como Funciona
- Factory: uma função mapeia entradas para uma instância concreta
- Builder: acumula estado, valida em
build(), evita construtores telescópicos @dataclass+field(default_factory=...)frequentemente substituem builders para casos simples- Classmethods (
from_env,parse) são factories idiomáticas do Python - Injeção de dependência passa factories em vez de instâncias para vinculação tardia
Factory vs Builder
| Padrão | Resolve | Idioma Python |
|---|---|---|
| Factory | Qual classe instanciar | função ou @classmethod |
| Builder | Muitos passos de montagem opcionais | builder fluente ou dataclass aninhado |
Notas Python
@dataclass
class Settings:
debug: bool = False
@classmethod
def from_env(cls, env: dict) -> "Settings":
return cls(debug=env.get("DEBUG") == "1")Armadilhas
- God factory - uma função com 20 ramificações
elif. Correção: dicionário de registro ou factories de subclasses por domínio. - Builder sem validação - estado parcial inválido escapa. Correção: valide apenas em
build(), mantenha o builder mutável internamente. - Exagero de engenharia em dataclasses simples - três campos opcionais não precisam de um builder. Correção: use padrões e
dataclass. - Factory ocultando efeitos colaterais - criar um cliente não deve conectar à rede. Correção: separe
createdeconnect. - Reutilização de builder mutável - reutilizar o mesmo builder após
build()causa interferência. Correção: documente o uso único ou clone embuild().
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Padrões @dataclass | Poucos campos opcionais | Validação complexa entre campos |
| Modelos Pydantic | Validação + análise de env/JSON | Scripts sem dependências |
Construtor **kwargs | Apenas biblioteca interna | API pública precisa de assinaturas estáveis |
| OOP de factory abstrata | Famílias de produtos relacionados | Linha de produto única |
FAQs
Preciso de uma classe Builder em Python?
Muitas vezes não. dataclass com padrões, model_validate ou uma única função factory cobrem muitos casos. Builders ajudam quando a montagem é multi-etapa e a ordem importa.
Onde as factories devem residir?
No módulo que possui os tipos de produto, ou um factories.py dedicado quando a criação cruza subpacotes.
Como as factories ajudam nos testes?
Passe um callable factory que retorna fakes/mocks. Produção usa a factory real; testes injetam lambda: FakeStorage().
Classmethods são factories?
Sim. from_dict, from_url e parse são métodos factory padrão do Python.
Builders podem ser dataclasses?
Use uma classe builder mutável ou dataclass com frozen=False internamente; congele o produto com frozen=True.
E sobre __init_subclass__ para registro?
Frameworks de plugin registram subclasses automaticamente - uma forma de descoberta de factory.
Como Pydantic se encaixa?
Model.model_validate(data) é uma factory validada a partir de dicionários/JSON - prefira-a para objetos de configuração.
build() deve retornar uma cópia?
Se o produto for mutável, retornar uma cópia profunda impede que os chamadores modifiquem o estado do builder em cache.
Factory vs contêiner de injeção de dependência?
Contêineres de DI chamam factories/provedores. Aplicações simples usam funções simples sem um framework.
Como tipar uma factory?
Callable[[], Storage] ou Protocol com um método create(). Genéricos quando o tipo do produto varia.
Relacionado
- Injeção de Dependência em Python - conectando factories
- Os Padrões Strategy & Dispatch - factory + strategy
- Null Object & Sentinels - instâncias de produto padrão
- Noções Básicas de Padrões Pythonicos - visão geral da seção
Versões da Stack: Esta página foi escrita para Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.