Alura > Cursos de Programação > Cursos de Python > Conteúdos de Python > Primeiras aulas do curso Princípios SOLID com Python: construindo códigos eficientes e escaláveis

Princípios SOLID com Python: construindo códigos eficientes e escaláveis

S - Responsabilidade Única - introducao

Apresentando o instrutor e o curso

Olá! Meu nome é Guilherme e trabalho com tecnologia há mais de 7 anos, em diferentes empresas, desde startups até grandes companhias. Já atuei como engenheiro de dados, engenheiro de software, líder técnico e, atualmente, estou como arquiteto de soluções.

Audiodescrição: Guilherme é uma pessoa parda com cabelos escuros e olhos castanhos. À sua direita, há uma luz refletindo azul com uma lousa, além de uma representação do funk pop entre a briga do Naruto e do Sasuke. À sua esquerda, está sua cama e um teclado musical.

Introduzindo o conceito de SOLID

No curso, iremos abordar como o SOLID funciona e como podemos colocá-lo em prática em nossos projetos. Iremos analisar letra por letra, entendendo como as abstrações são a chave para construir um projeto robusto.

A engenharia de software, ao contrário de outras engenharias que são mais rígidas, é mais volátil. O software pode ser comparado a um organismo vivo, que muda constantemente. As abstrações e fundamentos de engenharia de software, como o SOLID, nos ajudam a construir algo mais duradouro, que está preparado para as mudanças que ocorrerão ao longo do tempo.

Explorando o projeto prático

Para nos apoiar, vamos explorar esse projeto na prática, abordando o carrinho de compras de um e-commerce, para que possamos entender como realmente funciona e como podemos implementar em nosso dia a dia. Além dos vídeos, teremos atividades e o fórum para eventuais dúvidas.

Vamos aprender mais sobre engenharia de software e seus fundamentos? Estamos aguardando vocês!

S - Responsabilidade Única - Caos no código

Comparando complexidade em códigos e situações cotidianas

Já imaginamos ter que participar de uma partida de futebol e realizar um cruzamento e cabeceio ao mesmo tempo? Ou, ainda pior, adquirir um veículo que possui apenas um único pedal, onde um toque significa acelerar e dois toques significam frear? Imagine o caos que isso causaria. Não existe atacante que também seja goleiro. Esses exemplos são claros, mas continuamos desenvolvendo códigos extremamente complexos, com inúmeras responsabilidades concentradas em um único local.

Se ainda não enfrentamos esse tipo de código ou problema, é provável que o façamos em nossa carreira, pois é bastante comum ao construir um software. Muitas vezes, começamos a atribuir tarefas a um único local, onde tudo se originou. Assim, surge o que chamamos de "classe Deus", que possui inúmeras responsabilidades e é gigantesca. No entanto, o grande problema não é o tamanho ou a quantidade de linhas do código, mas sim o que ele se propõe a fazer.

Explorando problemas de responsabilidade em classes

Imagine que estamos construindo uma plataforma para uma concessionária online. Inicialmente, o veículo possui placa, registro, descrição, cor, modelo, entre outros aspectos simples. No entanto, novas funcionalidades surgem. O gerente de produto nos apresenta novos requisitos, como verificar quantos veículos estão em estoque, qual é o preço, o custo de logística para transportar o carro até o cliente, quantas multas o carro possui, se há IPVA atrasado, entre outras responsabilidades. Isso pode gerar dúvidas.

Quando iniciamos um projeto, podemos concentrar tudo em um único local, em uma única classe. Com o tempo, essa classe começa a perder sua origem e propósito. Embora o domínio ainda seja o veículo, e a descrição e o preço estejam diretamente relacionados a ele, os débitos veiculares estão vinculados à placa e ao registro, mas não necessariamente precisam estar no mesmo local.

Introduzindo o conceito de Single Responsibility

Esse tipo de problema dificulta a manutenção. Quando precisamos alterar débitos veiculares, o veículo pode deixar de aparecer na tela do e-commerce.

Onde está o problema? Como podemos resolver esse tipo de questão? Surge então o conceito de Single Responsibility, que afirma que uma única responsabilidade deve ser atribuída a uma classe. No livro Clean Architecture, essa abordagem é apresentada de forma um pouco diferente, sugerindo que uma classe deve ter um único ator para modificá-la. Por exemplo, se estamos falando de um veículo, a classe deveria ser responsável apenas por aspectos relacionados ao veículo, como descrição, renascimento, placa, modelo, ano, quilometragem, entre outros.

Separando responsabilidades em classes distintas

Se surgir uma nova responsabilidade, mesmo que esteja relacionada ou próxima à classe existente, ela deveria ser separada em uma nova classe. Um exemplo disso são os débitos veiculares. Podemos ter débitos que alteram a placa, como DPVAT, IPVA, multas e afins. Cada um desses tipos pode estar ativo ou inativo, com sua própria responsabilidade e lógica.

Perceba que não estamos falando de ter uma única responsabilidade no sentido de ter uma única função. No caso dos débitos, temos multas e IPVA, que são teoricamente diferentes, mas ambos são valores que descontamos do que vamos cobrar do veículo. Mesmo que sejam débitos diferentes, eles estão no mesmo lugar, pois não se trata de ter uma única responsabilidade nesse sentido, mas sim de convergir para uma única mudança. Se estamos alterando um local para mais de um ator, isso não deveria ocorrer.

Aplicando Single Responsibility na prática

O Single Responsibility nos ajuda a separar nosso projeto. No próximo vídeo, veremos isso na prática, aplicando em um software que nasce grande, com várias responsabilidades, e decidindo corretamente onde cada parte do projeto deve ficar. Vamos focar na prática, aplicando em um carrinho de compras, onde encontraremos um software com inúmeras responsabilidades e o quebraremos em partes menores. Escolheremos corretamente onde cada funcionalidade deve ficar, identificando os microdomínios e subdomínios dentro do nosso domínio.

Para isso, utilizaremos um e-commerce fictício e escolheremos especificamente um microserviço de carrinho como nosso domínio de trabalho. Iremos montar as classes corretas para eles, com suas respectivas responsabilidades. Nos encontramos no próximo vídeo.

S - Responsabilidade Única - Analisando projetos com problemas

Discutindo a aplicação do princípio da responsabilidade única

Agora vamos discutir como aplicar o princípio da Responsabilidade Única na prática. Este é o nosso projeto de carrinho, que começa com a criação do arquivo main.py na raiz do projeto. Vamos abri-lo. Nele, encontramos duas classes que serão utilizadas ao longo do projeto, e vamos dividi-las em partes menores.

Temos uma classe que representa os produtos a serem adicionados ao carrinho, contendo nome, preço, tipo e descrição, que será adicionada posteriormente. Além disso, temos a classe carrinho, que possui algumas funcionalidades.

Definindo a estrutura básica do projeto

Para começar, vamos definir a estrutura básica do nosso projeto. Primeiro, criamos uma lista que funcionará como nosso banco de dados fictício:

database = []

Criando a classe Produto

Agora, vamos criar a classe Produto. Inicialmente, definimos o método __init__ para inicializar a classe com os atributos básicos: nome, preco e tipo.

class Produto:
    def __init__(self, nome, preco, tipo):
        self.nome = nome
        self.preco = preco
        self.tipo = tipo

Criando a classe Carrinho

Em seguida, definimos a classe Carrinho, que terá funcionalidades como adicionar e remover produtos, calcular descontos, finalizar compras e notificar clientes.

class Carrinho:
    def __init__(self):
        self.items = []
        self.total = 0

Implementando o método adicionar

O método adicionar permite inserir novos produtos no carrinho, realizando um append na lista de itens e atualizando o total.

    def adicionar(self, produto):
        self.items.append(produto)
        self.total += produto.preco
        if produto.tipo == 'perecivel':
            print("Adicionar embalagem especial")
        elif produto.tipo == 'digital':
            print("Gerar link de download")
        else:
            print("Produto adicionado ao carrinho")

Implementando o método remover

O método remover verifica se o produto está no carrinho e o remove, atualizando o total.

    def remover(self, produto):
        if produto in self.items:
            self.items.remove(produto)
            self.total -= produto.preco
            print(f"{produto.nome} removido do carrinho.")
        else:
            print(f"{produto.nome} não está no carrinho.")

Implementando o método calcular desconto

Podemos calcular descontos aplicando cupons específicos, como BLACKFRIDAY ou NATAL.

    def calcular_desconto(self, cupom):
        if cupom == 'BLACKFRIDAY':
            self.total *= 0.5
        elif cupom == 'NATAL':
            self.total *= 0.9
        else:
            print("Cupom inválido")

Implementando o método finalizar compra

O método finalizar_compra exibe o total a pagar, salva os itens no banco de dados e envia uma confirmação por e-mail.

    def finalizar_compra(self):
        print(f"Total a pagar: R${self.total}")
        self.salvar_database()
        print("Email enviado com confirmação!")

Implementando o método salvar no banco de dados

Este método salva os itens do carrinho no banco de dados fictício.

    def salvar_database(self):
        database.append(self.items)
        print("Itens do carrinho salvos no banco")

Implementando métodos de notificação

Podemos enviar notificações para o cliente via SMS, e-mail ou WhatsApp.

    def send_sms(self, mensagem):
        print(mensagem)

    def send_email(self, mensagem):
        print(mensagem)

    def send_whatsapp(self, mensagem):
        print(mensagem)

    def notificar_cliente(self, itens, total, canal):
        message = f"Seus {itens}, estão a caminho, já debitamos R${total} do seu cartão"
        if canal == "sms":
            self.send_sms(message)
        elif canal == "email":
            self.send_email(message)
        elif canal == "whatsapp":
            self.send_whatsapp(message)
        else:
            raise TypeError("modelo ainda não suportado")

Melhorando a classe Produto

Agora, vamos aprimorar a classe Produto adicionando novos atributos e métodos. Vamos criar um método __init__ mais completo, que inclui SKU, preço, descrição e quantidade de estoque.

class Produto:
    def __init__(self, sku, preco, descricao, qtd_estoque):
        self._sku = sku
        self._preco = preco
        self._descricao = descricao
        self._qtd_estoque = qtd_estoque

Criando métodos da classe Produto

Vamos criar métodos para acessar os atributos de forma segura.

    def get_preco(self):
        return self._preco

    def get_estoque(self):
        if self._qtd_estoque > 0:
            result = True
        else:
            result = False
        return result

    def get_product(self):
        return {
            "sku": self._sku,
            "preco": self._preco,
            "descricao": self._descricao,
            "qtd_estoque": self._qtd_estoque
        }

Concluindo e preparando para o próximo vídeo

Com essas melhorias, nosso projeto está mais robusto. No próximo vídeo, trabalharemos na classe carrinho, onde começaremos a detalhar suas funcionalidades, identificando quais devem ou não ser implementadas.

Sobre o curso Princípios SOLID com Python: construindo códigos eficientes e escaláveis

O curso Princípios SOLID com Python: construindo códigos eficientes e escaláveis possui 223 minutos de vídeos, em um total de 63 atividades. Gostou? Conheça nossos outros cursos de Python em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Escolha a duração do seu plano e aproveite até 44% OFF

Conheça os Planos para Empresas