Programação Orientada a Objetos: Fundamentos que todo Dev precisa conhecer
Fala Dev, tudo em riba?
Na era da IA onde criamos um prompt simples e partir dele é gerado um emaranhado de código, pode soar estranho escrever um artigo sobre Programação Orientada a Objetos. Pode até ser que em um futuro próximo não escrevamos mais código algum, zero linhas, zero correções pequenas ou qualquer intervenção, porém esse dia ainda não chegou e mesmo com prompts muito bem elaborados (para não dizer gigantescos), a IA escorrega na implementação e na maioria das vezes é mais rápido nós corrigirmos o que está errado do que ficar escrevendo prompts e mais prompts para que a própria IA corrija o problema que ela mesma criou.
É preciso ter em mente que você precisa saber “instruir” o que a IA precisa fazer. A IA não raciocina por você, ela não vai sugerir algo do tipo “olha, diante desse prompt, vou criar um código Orientado Objetos, que aplique o Design Pattern X para resolver esse problema, pois é melhor e o Design Pattern Y para resolver aquele outro problema proposto no prompt”, ela simplesmente não vai fazer isso. Você é que tem que direcioná-la e para isso precisa saber como direcionar.
No artigo anterior escrevi sobre Design Patterns (Padrões de Projetos), sua definição, categorização, quando utilizar e quando não utilizar. Se você ainda não leu esse artigo, recomendo a leitura. Achei pertinente abordar a Programação Orientada a Objetos (POO) antes de prosseguir com a sequência de artigos sobre Design Patterns, pois os patterns resolvem problemas de software utilizando o paradigma da POO. Então sem mais delongas, vamos ao assunto.
O que é essa tal Programação Orientada a Objetos?
Programação Orientada a Objetos (POO) é uma forma de pensar e organizar o código baseando-se no conceito de objetos, que representam entidades do mundo real (ou do domínio do sistema) com características e comportamentos próprios.
Ao invés de escrevermos o código com instruções e funções soltas, você modela o seu sistema como um conjunto de objetos que se comunicam entre si.
Componentes de um Objeto
Antes de olhar o código para entender o que é um objeto e como ele é estruturado, precisamos primeiro entender que um objeto é composto por dois componentes: Atributos e Métodos.
Atributos
São as propriedades, as características de um objeto. Fazendo uma analogia rápida, pense em um objeto Carro, quais seriam os atributos dele? Podemos citar cor, fabricante, quantidade de portas, quantidade passageiros que transporta, modelo, ano de fabricação, classificação (sedã, SUV, pickup, etc.) e por ai vai.
Os atributos são informados no momento que instanciamos, criamos o objeto. Veremos isso mais adiante.
Métodos
Os métodos definem o comportamento do objeto, o que ele pode fazer. Voltando a analogia do carro, os possíveis métodos, comportamentos que o carro pode ter poderia ser desativar o alarme, ligar o motor, abrir vidro, acender farol e por ai vai.
Os métodos são requisitados de várias formas, como por exemplo um método principal que aciona os demais métodos, ou requisitar cada método a medida que necessário. Enfim, o comportamento depende muito do objeto que precisa ser criado.
Exemplo no mundo real
Para entender melhor sobre Programação Orientada a Objetos vamos a um exemplo prático e com que você talvez se depare em algum momento, afinal dificilmente você vai criar um objeto Carro nas suas demandas do dia a dia 😅.
Vamos criar um objeto Conta Bancária, que terá atributos (características) e métodos (comportamentos). O código do exemplo está escrito em Typescript.
class ContaBancaria {
private numeroConta: string;
private titular: string;
private saldo: number;
constructor(numeroConta: string, titular: string) {
this.numeroConta = numeroConta;
this.titular = titular;
this.saldo = 0;
}
depositar(valor: number): void {
this.saldo += valor;
console.log(`Depósito de R$${valor} realizado. Saldo atual: R$${this.saldo}`);
}
sacar(valor: number): void {
if (valor > this.saldo) {
throw new Error("Saldo insuficiente.");
}
this.saldo -= valor;
console.log(`Saque de R$${valor} realizado. Saldo atual: R$${this.saldo}`);
}
consultarSaldo(): number {
return this.saldo;
}
}
// Criando um objeto a partir da classe
const minhaConta = new ContaBancaria("0001-2", "Pedro");
minhaConta.depositar(500);
minhaConta.sacar(200);
console.log(minhaConta.consultarSaldo()); // 300
Entendendo o código:
- class: É o molde, o modelo. Define como os objetos desse tipo vão se comportar e é claro, o nome do objeto quando ele for instanciado (criado);
- constructor: Método especial executado automaticamente na criação do objeto, responsável por inicializar os atributos (características) do objeto;
- private: Modificador de acesso que impede que o atributo seja manipulado diretamente de fora da classe. Isso já é um dos pilares da POO que veremos em detalhes mais adiante em um outro artigo;
- depositar, sacar, consultarSaldo: São os métodos, ou melhor dizendo, os comportamentos que o objeto tem;
- new: O operador que instancia (cria) o objeto a partir da classe (class);
- minhaConta: É a criação concreta a partir da classe, é um objeto do tipo ContaBancaria.
Repare que, ao criar o objeto minhaConta do tipo ContaBancaria, são informados dois atributos, o número da conta (0001-2) e o titular (Pedro). Isso foi necessário devido a existência do método especial constructor que solicita tais atributos para que ele consiga inicializar.
Não é obrigatório que toda classe tenha um método constructor, e também não é obrigatório que o constructor receba atributos toda vez que ele foi incluído na classe. A utilização dele vai muito ao encontro do objeto que você necessita criar.
Nas três últimas linhas do código, requisitamos os métodos depositar, passando o valor de 500, o sacar que retirou da conta 200 e o consultarSaldo para sabermos o saldo existente na conta, ou seja, requisitamos comportamentos do objeto que, nesse caso são acionados de forma independente.
No entanto, se analisarmos o método sacar, ele faz uma verificação da existência do saldo. Para isso utiliza o this que significa que está acessando um atributo (saldo) da própria classe. Podemos utilizar o this para acessar também um método da própria classe.
Não vou entrar a fundo no modificador private, pois esse carinha é um assunto para um próximo artigo.
Por que usar o paradigma de Programação Orientada a Objetos?
Até essa altura você já deve ter entendido o básico do POO, porém deve estar se perguntando: Pra que ou por que devo usar isso? Que vantagens isso me traz? Respondendo rapidamente:
- Organização: Cada entidade do sistema (Conta Bancária, Titular da Conta, Transações Financeiras, Produtos, etc.) vira uma classe com seus atributos e comportamentos bem definidos;
- Reaproveitamento de código: Evita repetição de código ao utilizar herança e composição (calma, calma, não abordamos isso nesse artigo pois aprofundaremos esses assuntos nos próximos artigos);
- Manutenibilidade: Facilita o crescimento do sistema sem criar caos no código, principalmente na hora da manutenção do mesmo;
Conclusão
A Programação Orientada a Objetos é um paradigma de programação que traz muitos benefícios principalmente para sistemas que, apesar de iniciarem pequenos, tem potencial de crescer e de se tornar gigantesco. O crescimento é controlado e, se o código for estruturado da forma correta, tanto a adição de novas funcionalidades quanto a manutenção das funcionalidades já existentes fica mais fácil, permitindo editar uma classe sem gerar impactos em outra classe ou parte do sistema.
Pode parecer um pouco complicado no início, porém assim que você começa a utilizar a POO, vai perceber que é mais simples do que parece.
Espero que esse artigo tenha contribuído para seu crescimento como Dev. Forte abraço e até mais!