Padrões de Design Comuns em Python

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.

Tags: Python design patterns singleton factory builder

Publicado em 6-7 17:41 por Thomas