Guia de estudo

Aula 20 · Seg, 29/06

Padrões de projeto

Singleton e Factory Method: receitas prontas pra problemas que se repetem.

Programadores resolvem os mesmos problemas de estrutura toda hora. Os padrões de projeto são as soluções que já viraram consenso. Hoje vemos os criacionais: Singleton (uma instância só) e as Factories — do Simple Factory (um parâmetro escolhe a classe) ao Factory Method (a subclasse do criador decide).

Singleton Simple Factory Factory Method criacionais

roteiro da aula

O que vamos aprender hoje

01

O que é um padrão

Solução pronta e testada pra um problema que reaparece no design de software. E o que são os criacionais.

02

Singleton

Garantir que uma classe tenha UMA única instância no programa inteiro — e todo mundo usa a mesma.

03

Simple Factory

O primeiro degrau: centralizar num lugar só a decisão de qual classe criar a partir de um parâmetro — matando o if/elif espalhado.

04

Factory Method

O destino (GoF): mover a decisão de qual objeto criar pra uma subclasse do criador, quando a criação varia por contexto.

ponte com a aula 19

De onde paramos — sabemos modelar e codar POO

  • 🗺️ Na aula 19 aprendemos a DESENHAR classes com UML: a caixa, a herança (△), a composição (◆) e a ABC com o estereótipo «abstract».
  • 🧱 E já dominamos as ferramentas: herança (07), ABC e @abstractmethod (10), polimorfismo (09), composição (05) e atributos de classe (03/06).
  • 🧩 Hoje a gente não aprende um recurso NOVO de Python — aprende a COMBINAR o que já sabe em arranjos que viraram receita pronta.
  • 📖 Padrão de projeto é isso: uma forma já testada de organizar classes pra resolver um problema recorrente. Vamos usar UML de novo pra desenhar cada um.

o problema

O problema: reinventar a roda toda vez

  • 🔁 Certos problemas de estrutura aparecem em TODO sistema: "preciso de uma única configuração compartilhada", "preciso decidir qual objeto criar a partir de um texto".
  • 💡 Programadores experientes já resolveram isso milhares de vezes — e as melhores soluções viraram consenso, com nome próprio. Isso é um padrão de projeto.
  • 🗣️ Padrão também é vocabulário comum: dizer "aqui é um Singleton" comunica a ideia inteira pro time numa palavra, sem explicar o código linha a linha.

definição

O que é um padrão de projeto

  • 📐 Padrão de projeto (design pattern) = uma solução pronta, testada e nomeada pra um problema que se repete no design de software orientado a objetos.
  • 🍳 Pense numa RECEITA de cozinha: ela não é o prato pronto, é o passo a passo. Você adapta os ingredientes, mas o método é o mesmo. O padrão é a receita.
  • 🎯 Existem dezenas de padrões, divididos em três famílias. Hoje focamos na primeira: os CRIACIONAIS — os que cuidam de COMO os objetos são criados.

de onde vêm

De onde vêm os padrões: a Gang of Four

  • 📖 Em 1994, quatro autores publicaram o livro Design Patterns: Elements of Reusable Object-Oriented Software. Eles ficaram conhecidos como a Gang of Four — a "Gangue dos Quatro" —, abreviada GoF.
  • 🗂️ Esse livro catalogou 23 padrões clássicos: deu nome, descrição e exemplo a cada um, e os organizou em três famílias. É a referência que consolidou o assunto — por isso você vê "padrão GoF" nos materiais.
  • Singleton e Factory Method são dois desses 23 padrões oficiais. Já o Simple Factory não está no catálogo: é um idiom (um costume da comunidade), não um padrão GoF — mas é um ótimo degrau pra chegar no Factory Method.

panorama

As famílias de padrões (e onde estamos)

item detalhe
Criacionais ⭐ Cuidam de COMO criar objetos. Singleton e Factory (de hoje) estão aqui. É o nosso foco.
Estruturais Cuidam de como montar objetos em estruturas maiores (ex.: Adapter, Composite). (Só citado.)
Comportamentais Cuidam de como os objetos conversam e dividem responsabilidades (ex.: Observer, Strategy — aula 21). (Só citado.)

singleton · definição

O que é o Singleton

  • 🎯 Singleton é um padrão criacional que garante que uma classe tenha UMA única instância no programa inteiro — e dá um ponto único de acesso a ela.
  • 🔁 Não importa quantas vezes você "crie" o objeto: você sempre recebe o MESMO. A classe controla a própria criação pra nunca existir mais de um.
  • 🌐 Como existe um só, todo o programa compartilha o mesmo objeto e o mesmo estado: o ponto único de acesso é o que mantém tudo em sincronia.

singleton · o problema

O problema do Singleton: cópias dessincronizadas

  • ⚙️ Imagine a configuração do sistema (tema, idioma, volume). Se cada tela cria a SUA ConfigSistema(), você acaba com várias cópias soltas pelo programa.
  • 😵 O usuário muda o tema pra escuro numa tela — mas as outras telas têm a própria cópia, ainda no tema claro. As configurações ficam dessincronizadas.
  • 🎯 O que a gente queria era: existir UMA configuração só no programa inteiro, e toda tela enxergar exatamente a mesma. Esse é o trabalho do Singleton.

singleton · o problema

Antes e depois do Singleton

O mesmo programa, dois jeitos de criar a configuração: várias cópias soltas (o problema) e uma instância única compartilhada (o Singleton).

Antes — cada um cria a sua

antes.py
# cada tela cria a SUA cópia
config_a = ConfigSistema()
config_b = ConfigSistema()

config_a.tema = "escuro"
print(config_b.tema)
# claro  ← b NÃO viu a mudança de a
# objetos diferentes, estados diferentes.

Depois — todos usam a mesma

depois.py
# Singleton: sempre a MESMA instância
config_a = ConfigSistema()
config_b = ConfigSistema()

config_a.tema = "escuro"
print(config_b.tema)
# escuro  ← b é o MESMO objeto que a
# mudou num, mudou em todos.
Singleton garante que ConfigSistema() devolva sempre o mesmo objeto. Não importa quantas vezes você chame — é uma instância só, compartilhada pelo programa inteiro.

singleton · ancorando a ideia

Analogia: o presidente do país

  • 🇧🇷 Um país tem UM presidente por vez. Não importa de qual estado você fala "o presidente" — todos se referem à mesma pessoa. Não existem dez presidentes paralelos.
  • 📺 Ou o controle remoto da TV da sala: tem um só. Quem chega depois não fabrica outro — pega o que já existe. Se some, todo mundo fica sem trocar de canal.
  • 🔑 Singleton é exatamente isso no código: a classe controla quantas instâncias dela existem, e a resposta é "sempre uma só".

singleton · o código

Singleton em Python: o __new__

O segredo é o __new__ — o método que o Python chama ANTES do __init__ pra criar o objeto. A gente intercepta e devolve sempre a mesma instância, guardada num atributo de classe.

🏷️
_instancia é um atributo de CLASSE (aula 03/06): é compartilhado por todas as chamadas, o lugar perfeito pra guardar "a única instância".
🚪
__new__ é o porteiro: se ainda não existe instância, cria uma; se já existe, devolve a mesma. Por isso config1 is config2True.
config.py
class ConfigSistema:
    _instancia = None    # atributo de CLASSE: guarda a única instância

    def __new__(cls):
        if cls._instancia is None:
            cls._instancia = super().__new__(cls)
            cls._instancia.tema = "claro"
        return cls._instancia

config1 = ConfigSistema()
config2 = ConfigSistema()

print(config1 is config2)     # True — é o MESMO objeto
config1.tema = "escuro"
print(config2.tema)           # escuro — mudou nos dois, porque é um só
print(config1.tema)           # escuro
Repare no is (não ==): is pergunta "é o MESMO objeto na memória?". Como só existe uma instância, config1 is config2 é True. O _instancia começa como None e só é preenchido na primeira vez.

⚠️ Não use __init__ nesta classe: como o __new__ devolve sempre a mesma instância, um __init__ rodaria a cada ConfigSistema() e zeraria o estado. Por isso o tema inicial vai dentro do __new__ — diferente do que você faz nas outras aulas.

singleton · o código

Saída no terminal

Rodando o config.py, é isto que aparece — a prova de que existe uma instância só:

True: config1 e config2 são o MESMO objeto. Pedimos a configuração duas vezes e recebemos a mesma.
🔗
Mudamos config1.tema e config2.tema também mudou: como é um objeto só, a alteração aparece em todos os lugares que o usam.
terminal
True
escuro
escuro
É a solução do problema do s7/s8: uma configuração única, sempre sincronizada. Quem pedir ConfigSistema() em qualquer canto do programa recebe esta mesma instância.

singleton · na UML

O Singleton em UML

O ConfigSistema que acabamos de codar, desenhado como caixa de classe (a notação da aula 19): dá pra marcar que ele é um Singleton sem ler uma linha de código.

Diagrama UML de uma classe ConfigSistema com o estereótipo «singleton» no topo, o atributo público tema e o atributo privado _instancia — a própria classe guardando a sua única instância.
Dois sinais marcam um Singleton no diagrama: o estereótipo «singleton» no topo, e o atributo privado - _instancia — a própria classe guardando a sua única instância. Guarde essa marca: você vai reencontrá-la no diagrama do estacionamento, mais à frente.

singleton · variação

Alternativa: @classmethod get_instancia

Há outra forma comum: em vez de mexer no __new__, expor um @classmethod get_instancia() (aula 06) que cria na primeira chamada e devolve a mesma daí em diante. Um Logger central é o caso clássico.

🧭
Aqui não se chama Logger() direto: pede-se sempre Logger.get_instancia(). O @classmethod recebe a própria classe (cls) e controla a criação.
📝
Como log1 e log2 são o mesmo objeto, as duas mensagens caem na MESMA lista — um log central que o programa inteiro compartilha.
logger.py
class Logger:
    _instancia = None

    def __init__(self):
        self.mensagens = []

    @classmethod
    def get_instancia(cls):
        if cls._instancia is None:
            cls._instancia = cls()
        return cls._instancia

    def registrar(self, msg):
        self.mensagens.append(msg)

log1 = Logger.get_instancia()
log2 = Logger.get_instancia()

print(log1 is log2)           # True — sempre o mesmo Logger
log1.registrar("sistema iniciou")
log2.registrar("usuario logou")
print(log1.mensagens)         # ['sistema iniciou', 'usuario logou']
Duas receitas pro mesmo padrão: __new__ (s10) intercepta a criação por dentro; get_instancia() (aqui) oferece uma porta de entrada controlada. Ambas guardam a instância em _instancia. Escolha uma — não misture.

singleton · amarrando com a POO

Conexão: o Singleton só junta o que você já sabe

  • 🏷️ Atributo de classe (aula 03/06): _instancia vive na classe, não no objeto — por isso todas as chamadas enxergam o mesmo valor.
  • 🔒 Encapsulamento (aula 04): o _ em _instancia sinaliza "detalhe interno, não mexa de fora". O controle da criação fica protegido dentro da classe.
  • 🔧 Dunder methods (aula 03): __new__ é primo do __init__ e do __str__ que você já usa — um método especial que o Python chama sozinho.
  • 🟰 is vs == (aula 03): is compara identidade (mesmo objeto na memória); == compara valor. O Singleton se prova com is.

singleton · quando NÃO usar

Armadilha: o Singleton é tempero, não prato principal

  • 🌐 Um Singleton é, no fundo, uma variável global disfarçada — todo mundo acessa o mesmo objeto. Usado demais, vira um emaranhado difícil de seguir.
  • 🧪 Ele dificulta o teste: como o estado é compartilhado e persiste, um teste pode "vazar" pro outro. Objetos independentes são mais fáceis de testar isoladamente.
  • 🎯 Use só quando faz sentido existir UM e apenas um: configuração do sistema, log central, pool de conexões. Se podem existir vários, NÃO é Singleton.

factory · definição

O que é uma Factory

  • 🏭 Uma Factory é um padrão criacional: em vez de criar o objeto direto na mão, você pede a um ponto dedicado que DECIDE qual classe instanciar e devolve o objeto pronto.
  • 🎯 Você diz O QUE quer (faz um pedido), não COMO construir. A lógica de "qual classe criar" fica num lugar só, separada do resto do código.
  • 🪜 Hoje veremos duas formas, da mais simples à mais flexível: o Simple Factory (uma função decide pelo parâmetro) e o Factory Method (a subclasse decide). Começamos pela dor que elas resolvem.

factory · o problema

O problema do Factory: o if/elif espalhado

  • 🍕 Imagine o sistema de uma pizzaria que prepara calabresa, margherita ou portuguesa. A partir de um texto ("calabresa", "margherita"...) você precisa criar a pizza certa.
  • 🌿 A solução ingênua é um if sabor == "calabresa": ... elif sabor == "margherita": ... — e esse mesmo if/elif acaba COPIADO em vários pontos do código.
  • 💥 Quando entra um sabor novo ("quatro queijos"), você tem que caçar TODOS os if/elif e atualizar cada um. Esquece um → bug. O if/elif está espalhado demais.

factory · ancorando a ideia

Analogia: o balcão da pizzaria

  • 🍕 Na pizzaria você não vai à cozinha montar a pizza. Você pede no balcão "quero uma calabresa" — e a COZINHA sabe QUAL pizza preparar e entrega pronta.
  • 🗣️ Você pede pelo SABOR; a cozinha decide COMO fazer e qual produto montar. Você não precisa conhecer a receita de cada pizza pra pedir a sua.
  • 🏭 Factory é esse balcão no código: um lugar ÚNICO que recebe o pedido (um texto) e devolve o objeto certo, já pronto. Você não dá new em nada.

factory · simple factory

Ato 1 — Simple Factory: a hierarquia + a Factory

Começamos pela forma mais simples: o Simple Factory. Uma ABC Pizza (aula 10) com as subclasses; depois a Factory — uma FUNÇÃO central com um dicionário que mapeia o texto pra CLASSE (sem parênteses!) e cria a pizza num lugar só.

🗂️
O dicionário guarda CLASSES, não objetos: Calabresa sem (). A classe é um valor como qualquer outro — dá pra colocar num dicionário.
🏭
tabela[sabor]() faz duas coisas: tabela[sabor] pega a classe certa, e o () a instancia. Se o sabor não existe, um ValueError claro.
pizzaria.py
from abc import ABC, abstractmethod

class Pizza(ABC):
    @abstractmethod
    def descricao(self):
        pass

class Calabresa(Pizza):
    def descricao(self):
        return "Calabresa com cebola"

class Margherita(Pizza):
    def descricao(self):
        return "Margherita com manjericão"

class Portuguesa(Pizza):
    def descricao(self):
        return "Portuguesa com ovo e ervilha"

# Simple Factory: uma FUNÇÃO central que mapeia o sabor -> a CLASSE (sem parênteses!)
def criar_pizza(sabor):
    tabela = {
        "calabresa": Calabresa,
        "margherita": Margherita,
        "portuguesa": Portuguesa,
    }
    if sabor not in tabela:
        raise ValueError(f"Sabor desconhecido: {sabor}")
    return tabela[sabor]()   # os parênteses criam o objeto aqui
Isto é o Simple Factory: a decisão de QUAL classe criar vem de um PARÂMETRO (sabor) e mora num único lugar — a função criar_pizza. Sabor novo? Adiciona uma linha no dicionário. Guarde o nome: "Simple" Factory, porque a escolha está num parâmetro só, sem hierarquia de Factories.

factory · simple factory

Simple Factory: usando — polimorfismo sem if/elif

Com a Factory pronta, o resto do programa fica limpo: pede a pizza pela Factory e trata todas como Pizza. O descricao() certo é chamado por polimorfismo (aula 09).

🔄
O for não sabe qual classe concreta está usando — só chama .descricao(). Cada pizza responde do seu jeito: é o polimorfismo da aula 09.
🧹
Nenhum if sabor == ... aqui. Toda a decisão ficou DENTRO da Factory. Este código nunca precisa mudar quando entra um sabor novo.
pizzaria.py
# polimorfismo: trato todas como Pizza, sem if/elif
for sabor in ["calabresa", "margherita", "portuguesa"]:
    print(criar_pizza(sabor).descricao())
Junte este trecho ao s17 num arquivo só pra rodar. A Factory concentra a decisão; o resto do código vira um loop polimórfico que não muda quando a lista de sabores cresce.

factory · simple factory

Simple Factory: a saída no terminal

Rodando o pizzaria.py (a hierarquia + a Factory + o loop), sai:

🎭
Três objetos diferentes, mesma chamada descricao() — cada um devolve seu próprio texto. A Factory entregou o objeto certo; o polimorfismo fez o resto.
Pra adicionar quatro queijos: criar a classe QuatroQueijos e somar uma linha no dicionário da Factory. O loop acima não muda nem uma vírgula.
terminal
Calabresa com cebola
Margherita com manjericão
Portuguesa com ovo e ervilha
Esta é a saída do Simple Factory: ABC + uma função Factory + polimorfismo. A Factory decide QUAL classe a partir do parâmetro, e o polimorfismo chama o método certo sem if/elif. Funciona — mas tem um limite, e é o que o próximo slide mostra.

factory · o degrau

A limitação do Simple Factory

O Simple Factory é um interruptor só: um parâmetro escolhe a classe. Mas e se a MESMA pizza for feita de jeitos diferentes dependendo do CONTEXTO — a calabresa paulistana (massa fina) e a carioca (borda com catupiry)?

O parâmetro incha

incha.py
# pra variar por contexto, o parâmetro
# vira uma combinação que explode:
def criar_pizza(sabor, cidade):
    if cidade == "sp" and sabor == "calabresa":
        return CalabresaPaulistana()
    elif cidade == "rj" and sabor == "calabresa":
        return CalabresaCarioca()
    # ... e cada cidade x sabor é mais um if
    # o if/elif que a gente matou VOLTOU.

Faltou um eixo

ideia.txt
A função central decide TUDO sozinha.
Quando o "como criar" varia por
contexto (cidade, filial, tema...),
ela vira um emaranhado de combinações.

Queríamos: cada CONTEXTO sabe fazer
a SUA pizza, sem inchar um lugar só.

Esse é o trabalho do Factory Method.
Simple Factory resolve "qual classe a partir de um texto". Quando o "como criar" precisa variar por contexto, a função central incha. O próximo degrau (Factory Method) tira essa decisão da função e devolve pra quem conhece o contexto: a SUBCLASSE.

factory · factory method (GoF)

Ato 2 — Factory Method: a decisão vai pra subclasse

O Factory Method (o padrão GoF) resolve isso: em vez de uma função central, criamos uma ABC Pizzaria (o Criador) com um criar_pizza() ABSTRATO. Cada subclasse de pizzaria sobrescreve esse método e devolve a SUA pizza. Quem decide qual produto é a SUBCLASSE — não um parâmetro.

🏗️
Pizzaria(ABC) é o Criador: define o contrato criar_pizza() (abstrato) e um entregar() que USA o produto sem saber qual classe concreta veio.
🧬
Cada subclasse sobrescreve criar_pizza() e devolve a SUA pizza. A decisão de QUAL produto não está num parâmetro — está na escolha da subclasse de pizzaria.
pizzarias.py
from abc import ABC, abstractmethod

class Pizza(ABC):
    @abstractmethod
    def descricao(self):
        pass

class CalabresaPaulistana(Pizza):
    def descricao(self):
        return "Calabresa paulistana (massa fina, muita mussarela)"

class CalabresaCarioca(Pizza):
    def descricao(self):
        return "Calabresa carioca (borda com catupiry)"

class Pizzaria(ABC):                  # o Criador
    @abstractmethod
    def criar_pizza(self):            # o factory method
        pass
    def entregar(self):              # usa o produto sem saber o tipo concreto
        pizza = self.criar_pizza()
        return f"Entregando: {pizza.descricao()}"

class PizzariaPaulistana(Pizzaria):
    def criar_pizza(self):
        return CalabresaPaulistana()

class PizzariaCarioca(Pizzaria):
    def criar_pizza(self):
        return CalabresaCarioca()
A diferença-chave: quem decide qual produto é a SUBCLASSE do Criador, não um parâmetro. O entregar() chama self.criar_pizza() sem saber o tipo concreto — é o método-base usando o produto por polimorfismo. Adicionar uma pizzaria mineira = criar uma subclasse nova, sem mexer em nenhuma função central.

factory · factory method (GoF)

Factory Method: usando e a saída

Cada contexto (cidade) é uma pizzaria diferente. O loop trata todas como Pizzaria e chama entregar() — cada uma cria e entrega a SUA pizza, por polimorfismo.

🎭
O for não sabe (nem precisa) qual pizzaria concreta está usando — só chama entregar(). Cada uma cria a sua pizza pelo criar_pizza() sobrescrito.
Pizzaria de outra cidade? Crie PizzariaMineira(Pizzaria) com seu próprio criar_pizza(). Nenhuma função central muda — a hierarquia só cresce com mais uma subclasse.
pizzarias.py
for pizzaria in [PizzariaPaulistana(), PizzariaCarioca()]:
    print(pizzaria.entregar())

# Saída no terminal:
# Entregando: Calabresa paulistana (massa fina, muita mussarela)
# Entregando: Calabresa carioca (borda com catupiry)
A escolha do produto varia por CONTEXTO (a subclasse de pizzaria), sem inchar uma função central — exatamente o que o Simple Factory não dava de graça. Esta é a forma GoF do padrão: Factory Method.

factory · factory method (GoF)

O diagrama do Factory Method

São DUAS hierarquias paralelas: a do Criador (Pizzaria «abstract» ◁ as pizzarias) e a do Produto (Pizza «abstract» ◁ as pizzas). Cada criador concreto «creates» o seu produto.

Diagrama UML do Factory Method: à esquerda a hierarquia do Criador — Pizzaria como classe abstrata (métodos criar_pizza e entregar) com PizzariaPaulistana e PizzariaCarioca ligadas por seta de triângulo vazio (herança); à direita a hierarquia do Produto — Pizza como classe abstrata (método descricao) com CalabresaPaulistana e CalabresaCarioca ligadas por herança; e setas tracejadas «creates» de cada pizzaria concreta para a sua pizza concreta (PizzariaPaulistana cria CalabresaPaulistana, PizzariaCarioca cria CalabresaCarioca).
Leia o diagrama: a esquerda é o Criador (Pizzaria △ Paulistana/Carioca), a direita é o Produto (Pizza △ as calabresas), e as setas tracejadas «creates» mostram quem cria quem. É exatamente o que vocês desenharam com a UML da aula 19 — o factory method criar_pizza() é o ponto onde o Criador decide o Produto.

factory · amarrando com a POO

Conexão: o Factory Method só junta o que você já sabe

  • 🧬 Herança + ABC (aulas 07 e 10): o Criador Pizzaria é uma ABC com criar_pizza() abstrato; cada pizzaria HERDA e sobrescreve. É o mesmo contrato de classe abstrata de sempre.
  • 🎭 Polimorfismo (aula 09): entregar() chama self.criar_pizza() sem saber a subclasse — o método-base usa o produto certo por polimorfismo. Sem ele, o padrão não existiria.
  • 🏗️ Ponte com o @classmethod (aula 06): lá um construtor alternativo (from_string) já era uma Factory simples. Hoje a Factory subiu de nível: escolhe entre classes (Simple Factory) e até entre contextos (Factory Method).
  • 🗺️ E é UML da aula 19: o diagrama do s23 (duas hierarquias △ + setas «creates») é o mesmo desenho que vocês aprenderam a ler — agora virando um padrão com nome próprio.

factory · escolhendo

Simple Factory vs Factory Method: quando usar cada

Os dois são Factories, mas a escolha vem de lugares diferentes. Simple Factory: a escolha está num PARÂMETRO. Factory Method: a escolha está na SUBCLASSE do criador.

Simple Factory — parâmetro

simple.txt
A escolha vem de um PARÂMETRO,
num lugar só (uma função central).

criar_pizza("calabresa")

Ótimo quando:
• o "como criar" NÃO varia por contexto
• você só precisa mapear texto -> classe

É o caso mais comum — e o mais simples.

Factory Method — subclasse

method.txt
A escolha vem da SUBCLASSE do criador.

PizzariaPaulistana().entregar()

Use quando:
• o "como criar" varia por CONTEXTO
  (cidade, filial, tema, plataforma)
• a criação cresce e ia inchar a função

Mais estrutura, em troca de extensão.
Honesto: Simple Factory basta na maioria dos casos — é simples e direto. Factory Method entra quando a criação precisa variar por contexto (ou tende a crescer assim). Não comece pelo complicado: suba o degrau só quando a dor aparecer.

factory · quando NÃO usar

Armadilha: nem todo if vira Factory

  • 🪶 Se só existe UMA classe a criar, ou um if simples que aparece em UM lugar só, montar uma Factory é exagero — over-engineering. Um if direto resolve.
  • ⚖️ Subir pro Factory Method tem um custo: duas hierarquias paralelas. Só vale quando o "como criar" varia mesmo por contexto. Se não varia, fique no Simple Factory.
  • 🎯 Regra de bolso: criou a Factory e ela só tem uma entrada? Provavelmente não precisava. Padrão é remédio pra dor real, não enfeite.

fixando os três

A frase-âncora: os três padrões de hoje

Três criacionais, três perguntas diferentes. Grave: Singleton = QUANTAS instâncias; Simple Factory = um PARÂMETRO escolhe; Factory Method = a SUBCLASSE escolhe.

Singleton — QUANTAS

singleton.txt
Pergunta: quantas instâncias existem?
Resposta: UMA só, sempre a mesma.

Mecanismo: __new__ / get_instancia
guardando _instancia.

Exemplo: ConfigSistema, Logger.

Simple Factory — PARÂMETRO

simple.txt
Pergunta: qual classe instanciar?
Resposta: um PARÂMETRO escolhe,
numa função central.

Mecanismo: dicionário {texto: Classe}
+ ABC + polimorfismo.

Exemplo: criar_pizza(sabor).

Factory Method — SUBCLASSE

method.txt
Pergunta: qual produto, por contexto?
Resposta: a SUBCLASSE do criador
decide, sobrescrevendo o método.

Mecanismo: ABC Criador + criar_X()
abstrato + subclasses.

Exemplo: PizzariaPaulistana().
Singleton controla QUANTAS instâncias (uma só). Simple Factory: um PARÂMETRO escolhe a classe. Factory Method: a SUBCLASSE do criador escolhe. Quando bater o olho num código, é essa pergunta que separa os três.

ao vivo · vamos construir juntos

Exercício ao Vivo: estacionamento com os dois padrões

Partindo do diagrama do próximo slide, a gente implementa juntos um sistema de estacionamento que usa Factory (pra criar o veículo a partir de um texto) e Singleton (o painel de controle, único no estacionamento inteiro). Acompanhem a tradução de cada parte pra código.

1

Leia o diagrama

Veiculo «abstract» é a base, com tarifa_hora() abstrato. Carro, Moto e Caminhao ──△ Veiculo (herança). PainelControle «singleton» é o painel único — uma instância só.

2

Camada 1 — a hierarquia (ABC + polimorfismo)

Veiculo(ABC) com @abstractmethod tarifa_hora(), e Carro (8,00), Moto (4,00), Caminhao (15,00) implementando cada um o seu valor.

3

Camada 2 — a Factory

criar_veiculo(tipo, placa) com o dicionário {"carro": Carro, ...} e um raise ValueError pro tipo desconhecido — a decisão de QUAL classe num lugar só.

4

Camada 3 — o painel (Singleton)

PainelControle com __new__ e _instancia, começando com vagas_livres = 100 e um registrar_entrada() que desconta uma vaga — sempre no MESMO painel.

ao vivo · vamos construir juntos

O diagrama do nosso sistema

Partimos deste diagrama. Leia as caixas e os estereótipos — e nos próximos slides a gente traduz cada parte pra código, camada por camada.

Diagrama UML do exercício: PainelControle marcado com o estereótipo «singleton» (atributo vagas_livres e método registrar_entrada); e Veiculo como classe abstrata com o método abstrato tarifa_hora(), tendo Carro, Moto e Caminhao ligados a ela por setas de triângulo vazio (herança).
Duas ideias no diagrama: a hierarquia Veiculo △ Carro/Moto/Caminhao (herança + ABC + polimorfismo) vira a Factory criar_veiculo; e o «singleton» do PainelControle vira o __new__ que devolve sempre o mesmo painel. Nos próximos slides, uma camada de cada vez.

resolução · camada 1

A hierarquia: Veiculo (ABC) e as subclasses

Começamos pela base da Factory: a ABC Veiculo com o método abstrato tarifa_hora() e as três subclasses, cada uma com a sua tarifa. É o triângulo △ do diagrama virando código.

📐
Veiculo(ABC) com @abstractmethod é o estereótipo «abstract» do diagrama (aula 10): define o contrato tarifa_hora(), mas não pode ser instanciada direto.
🎭
Cada subclasse devolve a sua tarifa — o mesmo método, respostas diferentes. É o que a Factory vai explorar logo em seguida.
estacionamento.py
from abc import ABC, abstractmethod

# Camada 1: hierarquia de veículos (ABC + polimorfismo)
class Veiculo(ABC):
    def __init__(self, placa):
        self.placa = placa
    @abstractmethod
    def tarifa_hora(self):
        pass

class Carro(Veiculo):
    def tarifa_hora(self):
        return 8.00

class Moto(Veiculo):
    def tarifa_hora(self):
        return 4.00

class Caminhao(Veiculo):
    def tarifa_hora(self):
        return 15.00

# cada subclasse responde à sua maneira (polimorfismo da aula 09)
carro = Carro("ABC-1A23")
moto = Moto("XYZ-9999")
caminhao = Caminhao("BRA-2E19")
print(f"{carro.placa}: R$ {carro.tarifa_hora():.2f}/h")        # ABC-1A23: R$ 8.00/h
print(f"{moto.placa}: R$ {moto.tarifa_hora():.2f}/h")          # XYZ-9999: R$ 4.00/h
print(f"{caminhao.placa}: R$ {caminhao.tarifa_hora():.2f}/h")  # BRA-2E19: R$ 15.00/h
Esta é a hierarquia que a Factory vai usar. Se você tentar Veiculo('XYZ') direto, o Python 3.9 recusa com: TypeError: Can't instantiate abstract class Veiculo with abstract method tarifa_hora — o contrato da ABC sendo cobrado.

resolução · camada 2

A Factory: criar_veiculo

Agora a Factory: um dicionário {texto: Classe} e um ValueError pro tipo desconhecido. Recebe o texto, devolve o objeto certo — num lugar só.

🗂️
O dicionário guarda as CLASSES (sem ()). tabela[tipo] pega a classe certa pelo texto, e (placa) a instancia passando a placa pro __init__.
🛡️
O raise ValueError protege contra texto inválido: pedir "barco" falha com uma mensagem clara, em vez de devolver None silencioso.
estacionamento.py
# Camada 2: a Factory de veículos
def criar_veiculo(tipo, placa):
    tabela = {
        "carro": Carro,
        "moto": Moto,
        "caminhao": Caminhao,
    }
    if tipo not in tabela:
        raise ValueError(f"Tipo de veículo desconhecido: {tipo}")
    return tabela[tipo](placa)   # pega a classe e instancia
A decisão de QUAL veículo criar mora só aqui. O resto do programa não precisa conhecer Carro, Moto ou Caminhao — pede pela Factory e recebe um Veiculo pronto. Aqui Simple Factory basta — o tipo vem de um parâmetro e não varia por contexto; não precisa de Factory Method.

resolução · camada 3

O painel: PainelControle (Singleton)

A última camada é o Singleton: o painel de controle do estacionamento, único no sistema inteiro. Usa __new__ + _instancia, igual ao ConfigSistema de antes.

1️⃣
__new__ + _instancia garantem um painel só: não importa quantas vezes alguém chame PainelControle(), é sempre o mesmo objeto.
🅿️
vagas_livres começa em 100 e cada registrar_entrada() desconta uma. Como o painel é único, a contagem fica certa no programa inteiro.
estacionamento.py
# Camada 3: o painel de controle (Singleton)
class PainelControle:
    _instancia = None

    def __new__(cls):
        if cls._instancia is None:
            cls._instancia = super().__new__(cls)
            cls._instancia.vagas_livres = 100
        return cls._instancia

    def registrar_entrada(self):
        self.vagas_livres -= 1
Faz sentido ser Singleton: existe UM estacionamento, com UM painel e UM contador de vagas. Se cada caixa de entrada tivesse o seu painel, as contagens divergiriam — exatamente o problema do s7.

resolução · o código completo

Tudo junto: a resolução completa

As três camadas num arquivo só — é exatamente isto que você roda. Repare como Factory e Singleton convivem: a Factory cria cada veículo, e o painel único conta as entradas.

🏭
criar_veiculo(tipo, ...) é a Factory; PainelControle() dentro do loop é o Singleton — sempre o mesmo painel, somando as 4 entradas no mesmo contador.
▶️
Rodando este arquivo sai a tarifa de cada veículo e, no fim, a prova do Singleton (is) e do estado compartilhado (as vagas). A saída exata vem no próximo slide.
estacionamento.py
from abc import ABC, abstractmethod

# Camada 1: hierarquia de veículos (ABC + polimorfismo)
class Veiculo(ABC):
    def __init__(self, placa):
        self.placa = placa
    @abstractmethod
    def tarifa_hora(self):
        pass

class Carro(Veiculo):
    def tarifa_hora(self):
        return 8.00

class Moto(Veiculo):
    def tarifa_hora(self):
        return 4.00

class Caminhao(Veiculo):
    def tarifa_hora(self):
        return 15.00

# Camada 2: a Factory de veículos
def criar_veiculo(tipo, placa):
    tabela = {
        "carro": Carro,
        "moto": Moto,
        "caminhao": Caminhao,
    }
    if tipo not in tabela:
        raise ValueError(f"Tipo de veículo desconhecido: {tipo}")
    return tabela[tipo](placa)

# Camada 3: o painel de controle (Singleton)
class PainelControle:
    _instancia = None
    def __new__(cls):
        if cls._instancia is None:
            cls._instancia = super().__new__(cls)
            cls._instancia.vagas_livres = 100
        return cls._instancia
    def registrar_entrada(self):
        self.vagas_livres -= 1

# Tudo junto: a Factory cria, o painel único conta
entradas = [
    ("carro", "ABC-1234"),
    ("moto", "DEF-5678"),
    ("caminhao", "GHI-9012"),
    ("carro", "JKL-3456"),
]
for tipo, placa in entradas:
    veiculo = criar_veiculo(tipo, placa)   # Factory cria o veículo certo
    painel = PainelControle()              # sempre o MESMO painel
    painel.registrar_entrada()
    print(f"{tipo} {veiculo.placa}: R$ {veiculo.tarifa_hora():.2f}/h")

p1 = PainelControle()
p2 = PainelControle()
print(f"Mesmo painel? {p1 is p2}")
print(f"Vagas livres: {p1.vagas_livres}")
Dois padrões num sistema só: a Factory decide QUAL veículo criar; o painel garante QUANTOS painéis existem (um). Cada um resolve uma pergunta diferente, e juntos formam o sistema.

resolução · saída esperada

A saída no terminal

Rodando o estacionamento.py, é isto que aparece no terminal:

🏭
As quatro primeiras linhas vêm da Factory + polimorfismo: a Factory criou o veículo certo pra cada texto, e cada um devolveu sua própria tarifa.
1️⃣
Mesmo painel? True prova o Singleton (p1 is p2); Vagas livres: 96 prova o estado compartilhado: as 4 entradas desceram de 100 no MESMO painel.
terminal
carro ABC-1234: R$ 8.00/h
moto DEF-5678: R$ 4.00/h
caminhao GHI-9012: R$ 15.00/h
carro JKL-3456: R$ 8.00/h
Mesmo painel? True
Vagas livres: 96
96 = 100 − 4 entradas, todas no mesmo objeto. Se cada PainelControle() criasse um painel novo, as vagas teriam ficado em 99 quatro vezes. O True e o 96 são as duas provas de que o Singleton funcionou.

recapitulando

Glossário rápido de hoje

item detalhe
Padrão de projeto Solução pronta, testada e nomeada pra um problema recorrente no design de software OO.
GoF (Gang of Four) Apelido dos 4 autores do livro Design Patterns (1994), que catalogou 23 padrões clássicos. "Padrão GoF" = um desses padrões oficiais. Simple Factory não é GoF — é um idiom.
Padrões criacionais A família que cuida de COMO os objetos são criados. Singleton, Simple Factory e Factory Method estão aqui.
Singleton Garante UMA única instância da classe no programa inteiro. Controla QUANTAS. Via __new__ + _instancia.
Simple Factory Uma função/método central que devolve a classe certa a partir de um PARÂMETRO (dicionário {texto: Classe}). Mata o if/elif espalhado. É um idiom, não um padrão GoF.
Factory Method Padrão GoF: um Criador abstrato com um método Factory que as SUBCLASSES sobrescrevem pra decidir o produto. A decisão vem da subclasse, não de um parâmetro.
__new__ Método especial chamado ANTES do __init__ pra criar o objeto. O Singleton o intercepta pra devolver sempre o mesmo.
is vs == is compara identidade (mesmo objeto na memória); == compara valor. O Singleton se prova com is. Aula 03.

O que dominamos hoje

  • Sei o que é um padrão de projeto e o que distingue a família dos criacionais.
  • Explico o problema que o Singleton resolve: evitar cópias dessincronizadas, garantindo uma instância única.
  • Implemento um Singleton em Python com __new__ + _instancia (e conheço a variação com @classmethod get_instancia).
  • Provo que um Singleton funciona usando is (mesmo objeto) e o estado compartilhado.
  • Implemento um Simple Factory: centralizo a criação num dicionário {texto: Classe} + ABC + polimorfismo, com ValueError pro tipo inválido — matando o if/elif espalhado.
  • Implemento um Factory Method (GoF): movo a decisão pra uma subclasse de um Criador abstrato, que sobrescreve o método Factory, e uso o produto polimorficamente via self.criar_x().
  • Sei a frase-âncora dos três: Singleton controla QUANTAS instâncias; Simple Factory deixa um PARÂMETRO escolher; Factory Method deixa a SUBCLASSE escolher.

exercícios

Mãos à obra: os exercícios

Quatro exercícios em três níveis: um de Singleton, um de Simple Factory, um de Factory Method, e um desafio que junta os padrões. Cada um é INDIVIDUAL e termina em arquivo entregável. Comece em sala; termine o que faltar em casa. Mire pelo menos um de cada nível.

01

Fácil · Singleton de conexão

Implemente uma classe ConexaoBanco como Singleton, usando __new__ e _instancia. No __new__, inicialize consultas_feitas = 0. Adicione um método consultar() que soma 1 a esse contador. Inclua um bloco de teste que cria db1 e db2, imprime db1 is db2 (deve dar True), faz três consultas alternando entre db1 e db2, e imprime db1.consultas_feitas (deve dar 3 — o estado é compartilhado). Entregue o arquivo .py. Foco: provar a instância única com is e o estado compartilhado.

02

Fácil/Médio · Simple Factory de pagamento

Crie uma ABC Pagamento com o método abstrato processar(valor) e três subclasses — PagamentoPix, PagamentoCartao e PagamentoBoleto — cada uma imprimindo do seu jeito (ex.: [PIX] processando R$ 50.00). Escreva a Factory criar_pagamento(tipo) com um dicionário {texto: Classe} e um raise ValueError pro tipo desconhecido. Inclua um bloco de teste que percorre ["pix", "cartao", "boleto"] e chama processar(50.00) em cada um (loop polimórfico, sem if/elif). Entregue o arquivo .py. Foco: dicionário de classes + ABC + polimorfismo.

03

Médio · Factory Method: do diagrama ao código

A partir do diagrama do próximo slide — um Criador abstrato Exportador «abstract» com o método Factory criar_relatorio() e um método gerar() que usa self.criar_relatorio(); e ExportadorPDF, ExportadorExcel e ExportadorHTML ──△ Exportador (herança), cada um sobrescrevendo criar_relatorio() pra devolver seu próprio produto (RelatorioPDF, RelatorioExcel, RelatorioHTML, cada um com um conteudo() que devolve algo do seu formato, ex.: [PDF] relatório gerado) — implemente todas as classes em Python. Inclua um bloco de teste que percorre uma lista de exportadores e chama gerar() em cada um (loop polimórfico — quem chama não sabe qual exportador é). A saída deve ser: [PDF] relatório gerado · [Excel] relatório gerado · [HTML] relatório gerado. Entregue o arquivo .py. Foco: Factory Method de verdade — a decisão do produto mora na subclasse do criador, não num parâmetro.

04

Desafio · Singleton + Factory Method juntos

Escolha UM mini sistema: (a) Loja virtual, (b) Jogo de RPG ou (c) App de transporte. Primeiro DESENHE o diagrama de classes numa ferramenta (draw.io ou PlantUML) com: 1 Singleton (estereótipo «singleton») e um Factory Method — um Criador abstrato com um método Factory sobrescrito por pelo menos 2 subclasses (herança △), cada uma criando um produto da sua própria hierarquia. Depois IMPLEMENTE a partir do SEU diagrama: o Singleton com __new__, o Criador como ABC com @abstractmethod, as subclasses sobrescrevendo o método Factory, __str__ no produto (pra identificar qual foi criado ao imprimir), e um teste que prova a instância única com is e usa os criadores num loop polimórfico. Entregue o diagrama em PDF e o código no arquivo .py. Foco: combinar Singleton e Factory Method no mesmo sistema (preparação pro projeto final da aula 25).

exercício 3 · do diagrama ao código

O diagrama do Exercício 3 — Exportador

Este é o diagrama do exercício 3. Leia as duas hierarquias — o Criador Exportador e o Produto Relatorio — e traduza cada parte pra código Python.

Diagrama UML de Factory Method: Exportador (criador abstrato) com criar_relatorio() e gerar(), e as subclasses ExportadorPDF, ExportadorExcel e ExportadorHTML herdando dele; Relatorio (produto abstrato) com conteudo(), e RelatorioPDF, RelatorioExcel e RelatorioHTML herdando; cada Exportador «cria» o seu Relatorio.
É a mesma forma do Factory Method da pizza: um Criador abstrato (Exportador) cujo método-fábrica criar_relatorio() cada subclasse sobrescreve, devolvendo um produto da hierarquia Relatorio. A seta tracejada «cria» liga cada criador ao seu produto.

Antes de entregar: confira

  • Meu Singleton prova a instância única com is (não só ==) e mostra o estado compartilhado.
  • Se usei Simple Factory: dicionário {texto: Classe} (classes SEM parênteses) + ValueError pro tipo inválido.
  • Se usei Factory Method: o Criador é uma ABC com método Factory @abstractmethod, cada subclasse o sobrescreve devolvendo seu produto, e o uso é polimórfico (via self.criar_x()).
  • As subclasses herdam de uma ABC com @abstractmethod, e o teste é um loop polimórfico (sem if/elif por tipo).
  • Os nomes, strings e comentários estão em PT-BR, e o arquivo .py roda do começo ao fim sem erro.
  • No desafio, entreguei o diagrama em PDF E o código .py, e o diagrama bate com o que implementei.
próxima aula

Aula 21 · 01/07

Padrões de projeto — Observer e Strategy

Vimos os criacionais (como CRIAR objetos). Na próxima, os COMPORTAMENTAIS: como os objetos se comunicam (Observer) e como trocar de algoritmo em tempo de execução (Strategy).

Observer Strategy padrões comportamentais