Padrões de Criação
Padrão Singleton
O Padrão Singleton garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela. Isso é útil quando você precisa de um único objeto para coordenar ações em todo o sistema, como gerenciar configurações ou conexões de banco de dados.
Implementação em Python
Em Python, podemos implementar o Singleton sobrescrevendo o método __new__.
class ConfiguracaoApp:
_instancia_unica = None
def __new__(cls, *args, **kwargs):
if not cls._instancia_unica:
cls._instancia_unica = super(ConfiguracaoApp, cls).__new__(cls, *args, **kwargs)
# Inicialização adicional pode ocorrer aqui
return cls._instancia_unica
def __init__(self):
# O __init__ pode ser chamado múltiplas vezes, mas a lógica de inicialização
# só deve ocorrer na primeira vez. Uma maneira de garantir isso é usar um flag.
if not hasattr(self, '_inicializado'):
self.dados = {"servidor": "localhost", "porta": 8080}
self._inicializado = True
def obter_configuracao(self, chave):
return self.dados.get(chave)
# Exemplo de uso
config1 = ConfiguracaoApp()
config2 = ConfiguracaoApp()
print(f"Config1 e Config2 são a mesma instância: {config1 is config2}")
print(f"Servidor: {config1.obter_configuracao('servidor')}")
config1.dados["porta"] = 9000
print(f"Nova porta via config2: {config2.obter_configuracao('porta')}")
Padrão Factory Method
O Factory Method define uma interface para criar objetos, mas deixa as subclasses decidirem qual classe instanciar. Ele permite que uma classe delegue a instanciação para subclasses.
Implementação em Python
Um exemplo clássico é a criação de diferentes tipos de usuários com permissões variadas.
from abc import ABC, abstractmethod
class Usuario(ABC):
def __init__(self, nome):
self.nome = nome
@abstractmethod
def obter_permissao(self):
pass
class UsuarioAdmin(Usuario):
def obter_permissao(self):
return "Admin"
class UsuarioConvidado(Usuario):
def obter_permissao(self):
return "Convidado"
class FabricaUsuario:
@staticmethod
def criar_usuario(nome, tipo_usuario):
if tipo_usuario == "admin":
return UsuarioAdmin(nome)
elif tipo_usuario == "convidado":
return UsuarioConvidado(nome)
else:
raise ValueError("Tipo de usuário desconhecido")
# Exemplo de uso
admin = FabricaUsuario.criar_usuario("Alice", "admin")
convidado = FabricaUsuario.criar_usuario("Bob", "convidado")
print(f"Usuário: {admin.nome}, Permissão: {admin.obter_permissao()}")
print(f"Usuário: {convidado.nome}, Permissão: {convidado.obter_permissao()}")
Padrão Builder
O padrão Builder separa a construção de um objeto complexo de sua representação, permitindo que o mesmo processo de construção crie diferentes representações.
Implementação em Python
Considere a construção de um objeto Computador com diferentes componentes.
from abc import ABC, abstractmethod
class Componente(ABC):
pass
class CPU(Componente):
def __init__(self, modelo):
self.modelo = modelo
class RAM(Componente):
def __init__(self, capacidade_gb):
self.capacidade_gb = capacidade_gb
class Armazenamento(Componente):
def __init__(self, tipo, capacidade_gb):
self.tipo = tipo # SSD ou HDD
self.capacidade_gb = capacidade_gb
class BuilderComputador(ABC):
@abstractmethod
def definir_cpu(self, cpu: CPU): pass
@abstractmethod
def definir_ram(self, ram: RAM): pass
@abstractmethod
def definir_armazenamento(self, armazenamento: Armazenamento): pass
@abstractmethod
def obter_computador(self): pass
class BuilderComputadorGamer(BuilderComputador):
def __init__(self):
self.computador = {"cpu": None, "ram": None, "armazenamento": None}
def definir_cpu(self, cpu: CPU):
self.computador["cpu"] = cpu
def definir_ram(self, ram: RAM):
self.computador["ram"] = ram
def definir_armazenamento(self, armazenamento: Armazenamento):
self.computador["armazenamento"] = armazenamento
def obter_computador(self):
# Simula a criação do objeto final
return self.computador
class Diretor:
def __init__(self, builder: BuilderComputador):
self._builder = builder
def construir_computador_basico(self):
self._builder.definir_cpu(CPU("Intel i5"))
self._builder.definir_ram(RAM(8))
self._builder.definir_armazenamento(Armazenamento("HDD", 1024))
def construir_computador_avancado(self):
self._builder.definir_cpu(CPU("AMD Ryzen 9"))
self._builder.definir_ram(RAM(32))
self._builder.definir_armazenamento(Armazenamento("SSD", 2048))
# Exemplo de uso
builder_gamer = BuilderComputadorGamer()
diretor = Diretor(builder_gamer)
diretor.construir_computador_basico()
computador_basico = builder_gamer.obter_computador()
print(f"Computador Básico: {computador_basico}")
diretor.construir_computador_avancado()
computador_avancado = builder_gamer.obter_computador()
print(f"Computador Avançado: {computador_avancado}")
Padrão Prototype
O Prototype especifica os tipos de objetos a serem criados usando uma instância protótipo e cria novos objetos copiando esse protótipo. É útil para reduzir o custo de criação de objetos complexos ou quando a inicialização é demorada.
Implementação em Python
Usaremos cópia profunda (copy.deepcopy) para garantir que os objetos clonados sejam independentes.
import copy
class Documento:
def __init__(self, titulo, autor, conteudo):
self.titulo = titulo
self.autor = autor
self.conteudo = conteudo
def __str__(self):
return f"Título: {self.titulo}\nAutor: {self.autor}\nConteúdo: {self.conteudo[:50]}..."
class GerenciadorPrototypes:
def __init__(self):
self._prototypes = {}
def registrar(self, nome_chave, objeto_prototype):
self._prototypes[nome_chave] = objeto_prototype
def clonar(self, nome_chave, **novos_atributos):
prototype = self._prototypes.get(nome_chave)
if not prototype:
raise ValueError(f"Prototype com a chave '{nome_chave}' não encontrado.")
objeto_clonado = copy.deepcopy(prototype)
objeto_clonado.__dict__.update(novos_atributos) # Atualiza atributos específicos
return objeto_clonado
# Exemplo de uso
gerenciador = GerenciadorPrototypes()
# Criação do protótipo original
documento_base = Documento(
titulo="Relatório Mensal",
autor="Equipe de Análise",
conteudo="Este é o conteúdo detalhado do relatório mensal, incluindo métricas e observações importantes..."
)
gerenciador.registrar("relatorio_mensal", documento_base)
# Clonando e modificando o protótipo
documento_janeiro = gerenciador.clonar(
"relatorio_mensal",
titulo="Relatório de Janeiro",
conteudo="Conteúdo específico de janeiro..."
)
documento_fevereiro = gerenciador.clonar(
"relatorio_mensal",
titulo="Relatório de Fevereiro",
autor="Novo Analista",
conteudo="Conteúdo específico de fevereiro..."
)
print("--- Documento Base ---")
print(documento_base)
print("\n--- Documento de Janeiro ---")
print(documento_janeiro)
print("\n--- Documento de Fevereiro ---")
print(documento_fevereiro)
print(f"\nDocumento base e clone de janeiro são o mesmo objeto? {documento_base is documento_janeiro}")
Padrões Estruturais
Padrão Adapter
O Adapter permite que interfaces incompatíveis colaborem. Ele atua como um intermediário, traduzindo a interface de uma classe para outra interface que o cliente espera.
Implementação em Python
Imagine que você tem um sistema legado com uma interface antiga e precisa integrá-lo a um novo sistema que espera uma interface diferente.
class SistemaLegado:
def operacao_antiga(self):
return "Resultado da operação antiga do sistema legado."
class NovoSistema:
def requisicao_moderna(self):
return "Resultado da requisição moderna do novo sistema."
class AdapterSistema(NovoSistema):
def __init__(self, sistema_legado: SistemaLegado):
self._sistema_legado = sistema_legado
def requisicao_moderna(self):
# Traduz a chamada da interface nova para a interface antiga
resultado_antigo = self._sistema_legado.operacao_antiga()
return f"Adaptado: {resultado_antigo}"
# Exemplo de uso
sistema_legado_instancia = SistemaLegado()
adapter = AdapterSistema(sistema_legado_instancia)
# O cliente usa a interface do NovoSistema, mas o adapter cuida da tradução
resultado = adapter.requisicao_moderna()
print(resultado)
Padrão Decorator
O Decorator anexa comportamentos adicionais a um objeto dinamicamente. Decorators fornecem uma alternativa flexível a subclasses para estender a funcionalidade.
Implementação em Python
Em Python, os decoradores de função são uma forma elegante de implementar este padrão.
import functools
def contador_chamadas(func):
@functools.wraps(func)
def wrapper_contador(*args, **kwargs):
wrapper_contador.num_chamadas += 1
print(f"Chamada {wrapper_contador.num_chamadas} da função {func.__name__}")
return func(*args, **kwargs)
wrapper_contador.num_chamadas = 0
return wrapper_contador
@contador_chamadas
def saudacao(nome):
"""Retorna uma saudação personalizada."""
return f"Olá, {nome}!"
@contador_chamadas
def calcular_soma(a, b):
"""Calcula a soma de dois números."""
return a + b
# Exemplo de uso
print(saudacao("Mundo"))
print(saudacao("Python"))
print(f"Número de chamadas para saudacao: {saudacao.num_chamadas}")
print(calcular_soma(5, 3))
print(calcular_soma(10, 20))
print(f"Número de chamadas para calcular_soma: {calcular_soma.num_chamadas}")
Padrão Facade
O Facade fornece uma interface unificada para um conjunto de interfaces em um subsistema. Ele define uma interface de nível mais alto que torna o subsistema mais fácil de usar.
Implementação em Python
Simplificando a interação com um sistema complexo, como um sistema operacional simulado.
class ServicoArquivo:
def iniciar(self):
print("Serviço de Arquivo iniciado.")
def parar(self):
print("Serviço de Arquivo parado.")
def criar_arquivo(self, nome):
print(f"Arquivo '{nome}' criado.")
class ServicoRede:
def iniciar(self):
print("Serviço de Rede iniciado.")
def parar(self):
print("Serviço de Rede parado.")
def conectar(self, host):
print(f"Conectado a {host}.")
class ServicoBancoDados:
def iniciar(self):
print("Serviço de Banco de Dados iniciado.")
def parar(self):
print("Serviço de Banco de Dados parado.")
def executar_query(self, query):
print(f"Executando query: {query}")
class FacadeSistema:
def __init__(self):
self._servico_arquivo = ServicoArquivo()
self._servico_rede = ServicoRede()
self._servico_bd = ServicoBancoDados()
def iniciar_sistema(self):
print("Iniciando o sistema...")
self._servico_arquivo.iniciar()
self._servico_rede.iniciar()
self._servico_bd.iniciar()
print("Sistema iniciado.")
def parar_sistema(self):
print("Parando o sistema...")
self._servico_bd.parar()
self._servico_rede.parar()
self._servico_arquivo.parar()
print("Sistema parado.")
def realizar_operacao_complexa(self, nome_arquivo, host, query):
print("\nRealizando operação complexa:")
self._servico_arquivo.criar_arquivo(nome_arquivo)
self._servico_rede.conectar(host)
self._servico_bd.executar_query(query)
# Exemplo de uso
facade = FacadeSistema()
facade.iniciar_sistema()
facade.realizar_operacao_complexa("dados.txt", "servidor.exemplo.com", "SELECT * FROM usuarios;")
facade.parar_sistema()
Padrão Flyweight
O Flyweight usa compartilhamento para suportar um grande número de objetos pequenos de forma eficiente. Ele é útil quando você tem muitos objetos com estado intrínseco compartilhado.
Implementação em Python
Simulando a renderização de árvores em uma floresta, onde o tipo e a aparência básica da árvore são compartilhados.
import random
class TipoArvore:
def __init__(self, nome, cor_folhagem, textura_tronco):
self.nome = nome
self.cor_folhagem = cor_folhagem
self.textura_tronco = textura_tronco
def __str__(self):
return f"{self.nome} (Folhagem: {self.cor_folhagem}, Tronco: {self.textura_tronco})"
class FabricaArvores:
_tipos_arvore = {}
@staticmethod
def obter_tipo_arvore(nome, cor_folhagem, textura_tronco):
chave = (nome, cor_folhagem, textura_tronco)
if chave not in FabricaArvores._tipos_arvore:
FabricaArvores._tipos_arvore[chave] = TipoArvore(nome, cor_folhagem, textura_tronco)
return FabricaArvores._tipos_arvore[chave]
class Arvore:
def __init__(self, tipo_arvore: TipoArvore, idade: int, posicao_x: int, posicao_y: int):
self.tipo = tipo_arvore
self.idade = idade
self.posicao_x = posicao_x
self.posicao_y = posicao_y
def desenhar(self):
print(f"Desenhando árvore: {self.tipo} na posição ({self.posicao_x}, {self.posicao_y}) com idade {self.idade}.")
def main_flyweight():
tipos_disponiveis = [
("Pinheiro", "Verde Escuro", "Rugoso"),
("Carvalho", "Verde Claro", "Texturizado"),
("Bordo", "Vermelho/Laranja", "Liso")
]
posicoes = [(random.randint(0, 100), random.randint(0, 100)) for _ in range(20)]
idades = [random.randint(1, 50) for _ in range(20)]
arvores_na_floresta = []
for i in range(20):
nome, cor, textura = random.choice(tipos_disponiveis)
tipo = FabricaArvores.obter_tipo_arvore(nome, cor, textura)
arvore = Arvore(tipo, idades[i], posicoes[i][0], posicoes[i][1])
arvores_na_floresta.append(arvore)
arvore.desenhar()
print(f"\nTotal de árvores criadas: {len(arvores_na_floresta)}")
print(f"Total de tipos de árvores únicos (compartilhados): {len(FabricaArvores._tipos_arvore)}")
# Exemplo de uso
main_flyweight()
Padrão Model-View-Controller (MVC)
MVC é um padrão arquitetural que separa a aplicação em três componentes interconectados: Modelo (dados e lógica de negócios), Visão (interface do usuário) e Controlador (manipula a entrada do usuário e atualiza Modelo e Visão).
Padrão Proxy
O Proxy fornece um substituto ou placeholder para outro objeto para controlar o acesso a ele. É útil para controle de acesso, lazy initialization ou logging.
Padrões Comportamentais
Padrão Chain of Responsibility
O Chain of Responsibility permite que uma requisição percorra uma cadeia de manipuladores. Cada manipuladro decide se processa a requisição ou a passa para o próximo na cadeia.
Padrão Command
O Command encapsula uma requisição como um objeto, permitindo parametrizar clientes com diferentes requisições, enfileirar ou registrar requisições e suportar operações que podem ser desfeitas.
Padrão Interpreter
O Interpreter define uma gramática para uma linguagem e um interpretador para essa gramática.
Padrão Observer
O Observer define uma dependência um-para-muitos entre objetos, de modo que quando um objeto (o sujeito) muda de estado, todos os seus dependentes (os observadores) são notificados e atualizados automaticamente.
Padrão State
O State permite que um objeto alterne seu comportamento quando seu estado interno muda. O objeto parecerá mudar de classe.
Padrão Strategy
O Strategy define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. O Strategy permite que o algoritmo varie independentemente dos clientes que o utilizam.
Padrão Template Method
O Template Method define o esqueleto de um algoritmo em uma operação, adiando alguns passos para subclasses. O Template Method permite que as subclasses redefinam certos passos de um algoritmo sem alterar sua estrutura.