Programação orientada a objetos: princípios e boas práticas
default
Faaaaaala, dev! Mayk Brito na área para falar sobre um tema que muita gente ouve falar, mas que às vezes fica meio nebuloso: a programação orientada a objetos. Se você já começou a explorar o mundo da programação, pode ter trombado com esse termo, conhecido também como POO. E olha, deixa eu te contar uma coisa: entender orientação a objetos muda a nossa forma de programar. Se você quer organizar melhor o seu código, deixar ele mais fácil de entender e até de reaproveitar em outros projetos, cola aqui comigo que eu vou te mostrar como a POO pode transformar sua vida como dev!

Mas... o que é programação orientada a objetos?

Imagina que você tem que organizar todas as suas ideias de programação. Um jeito de fazer isso é pensar em termos de objetos – coisas que têm características (ou propriedades) e funcionalidades (ou métodos). Assim como no mundo real, onde uma caneta, por exemplo, tem uma cor, um tipo de tinta e uma função bem específica (escrever!), na POO, tudo no nosso código pode ser estruturado dessa maneira.
A POO é um paradigma – ou seja, um jeito de pensar na programação – e foi criada para organizar o código em torno de objetos. Ela surgiu para facilitar o desenvolvimento de sistemas complexos, permitindo que a gente consiga dividir o código em pequenas partes que se comunicam e funcionam de forma mais independente. O objetivo deste artigo é simplificar esses conceitos e mostrar como aplicá-los na prática. Vem comigo!

Os 4 pilares da programação orientada a objetos

Para a POO fazer sentido, a gente precisa entender seus quatro pilares: encapsulamento, herança, polimorfismo e abstração. Eles são as peças fundamentais que fazem a mágica acontecer. Vamos conhecer cada um deles.

1. Encapsulamento: guardando os segredos

O encapsulamento é como uma “caixinha” onde você guarda tudo o que é importante para o funcionamento de um objeto, mas sem deixar todo mundo ver ou mexer. Por exemplo, pensa no motor de um carro: você só precisa girar a chave para ele funcionar. Todo o funcionamento do motor está escondido; só você sabe que ele está ali, encapsulado.
Em programação, isso significa proteger certas partes do código para que apenas o que é realmente necessário seja acessível do lado de fora do objeto. Imagine uma classe Curso na Rocketseat, onde o material completo está protegido e só é acessível para alunos que se inscreveram.
class Curso { #conteudoCompleto; constructor(nome, duracao) { this.nome = nome; this.duracao = duracao; this.#conteudoCompleto = "Material exclusivo para alunos"; } acessarConteudo() { return `Acessando conteúdo do curso ${this.nome}: ${this.#conteudoCompleto}`; } } const cursoJavaScript = new Curso("JavaScript", "3 meses"); console.log(cursoJavaScript.acessarConteudo());
No exemplo acima, o conteudoCompleto está encapsulado com #, então ele não pode ser acessado diretamente de fora da classe. Somente o método acessarConteudo consegue usá-lo, garantindo que o material do curso esteja acessível apenas da forma planejada. Esse tipo de encapsulamento ajuda a proteger informações e manter o controle sobre o que pode ser acessado.

2. Herança: aproveitando o que já existe

Herança é sobre reaproveitar. Imagine que você tem uma estrutura base para um curso, chamada Curso, e quer criar um curso específico para Node.js. Em vez de recriar tudo do zero, você usa a herança para que o curso de Node.js aproveite as propriedades e métodos da classe Curso, adicionando apenas o que é específico para ele.
class Curso { iniciar() { console.log("Iniciando o curso..."); } } class CursoNode extends Curso { assistirAula() { console.log("Assistindo aula de Node.js!"); } } const cursoNode = new CursoNode(); cursoNode.iniciar(); // Herda o método iniciar da classe Curso cursoNode.assistirAula(); // Método específico da classe CursoNode
Aqui, a classe CursoNode herda o método iniciar da classe base Curso. Assim, podemos adicionar funcionalidades específicas, como assistirAula, que é exclusiva do curso de Node.js. A herança permite reaproveitar o que já foi feito, organizando o código de forma eficiente e evitando redundâncias.

3. Polimorfismo: várias formas de ser

Polimorfismo permite que métodos em classes diferentes respondam de maneira distinta a uma mesma chamada. Imagine que estamos desenvolvendo uma plataforma de cursos para a Rocketseat, onde diferentes formações, como React e Java, possuem o método iniciar. Cada formação inicia de maneira única, mas o método iniciar é o mesmo.
class Formacao { iniciar() { console.log("Iniciando a formação..."); } } class FormacaoReact extends Formacao { iniciar() { console.log("Iniciando a formação de React!"); } } class FormacaoJava extends Formacao { iniciar() { console.log("Iniciando a formação de Java!"); } } const react = new FormacaoReact(); const java = new FormacaoJava(); react.iniciar(); // Inicia a formação de React java.iniciar(); // Inicia a formação de Java
No exemplo acima, tanto FormacaoReact quanto FormacaoJava possuem o método iniciar, mas cada um responde de forma única, respeitando o contexto da formação específica. Esse é o poder do polimorfismo: adaptar o comportamento de um método conforme a necessidade de cada classe.

4. Abstração: só o que importa

Abstração ajuda a focar nos aspectos essenciais e a esconder os detalhes complexos. Imagine que estamos criando um sistema para gerenciar cursos da Rocketseat, onde cada curso pode ser específico, como Go ou C#, mas todos compartilham características comuns de um Curso. Com a abstração, definimos o que é necessário em cada curso sem detalhar a implementação de cada um.
class Curso { constructor(nome) { if (new.target === Curso) { throw new Error("Curso é uma classe abstrata e não pode ser instanciada diretamente"); } this.nome = nome; } iniciarCurso() { throw new Error("Método iniciarCurso() deve ser implementado"); } } class CursoGo extends Curso { iniciarCurso() { return `${this.nome} está iniciando, se prepare para aprender Go!`; } } class CursoCSharp extends Curso { iniciarCurso() { return `${this.nome} está iniciando, se prepare para aprender C#!`; } } const go = new CursoGo("Curso de Go"); const csharp = new CursoCSharp("Curso de C#"); console.log(go.iniciarCurso()); console.log(csharp.iniciarCurso());
Nesse código, Curso é uma classe abstrata que define o método iniciarCurso, mas não o implementa. As classes CursoGo e CursoCSharp herdam de Curso e implementam o método conforme suas necessidades específicas. A abstração permite criar um modelo geral e deixar que cada curso detalhe sua implementação – simplificando a estrutura do sistema e mantendo a flexibilidade.

Princípios SOLID: levando a POO ao próximo nível

Agora que já falamos dos pilares, vamos explorar o SOLID. Esses cinco princípios elevam a POO, ajudando a manter o código mais limpo, organizado e fácil de expandir.
  • SRP (single responsibility principle): cada classe deve ter uma única responsabilidade. Isso evita que uma classe fique “fazendo tudo” e facilita a manutenção do código.
  • OCP (open/closed principle): classes devem estar abertas para extensão, mas fechadas para modificação. Ou seja, se você precisar adicionar funcionalidades, estenda a classe ao invés de modificá-la diretamente.
  • LSP (Liskov substitution principle): subclasses devem ser substituíveis por suas superclasses. Se você substitui uma classe base por uma derivada, o código ainda deve funcionar sem problemas.
  • ISP (interface segregation principle): interfaces específicas para cada tipo de cliente. Ou seja, as classes não devem depender de métodos que não usam.
  • DIP (dependency inversion principle): dependências devem apontar para abstrações, e não para implementações concretas.

Boas práticas de POO

Aqui vão algumas dicas para escrever código orientado a objetos que vai deixar sua vida e a de quem for ler seu código mais fácil, especialmente em sistemas robustos e escaláveis como os usados na Rocketseat:
  • Nomeação clara e direta: escolha nomes que sejam claros e significativos para variáveis, métodos e classes. Em vez de abreviações ou siglas, use nomes descritivos, como AlunoCursoNode ou FormacaoJavaScript. Nomes claros ajudam o time a entender o código sem precisar de muita documentação extra.
  • Comente apenas o essencial: escreva o código de forma que ele “fale por si”. Comentários devem esclarecer o que não é óbvio e explicar lógicas complexas ou específicas. Em uma formação da Rocketseat, por exemplo, ao definir um método iniciarModulo, deixe claro o que cada etapa do módulo faz, mas sem sobrecarregar o código com comentários excessivos.
  • Escreva testes unitários: testes são a garantia de que o código está funcionando como esperado e de que mudanças futuras não quebrem funcionalidades. Imagine um sistema de acesso a cursos onde os alunos podem iniciar e pausar aulas. Ter testes que garantem que os alunos só conseguem acessar o conteúdo correto é essencial para um sistema funcional e seguro.
  • Refatore sempre que possível: não tenha medo de revisar e reescrever partes do código para deixá-lo mais claro ou eficiente. Se você percebeu uma oportunidade de simplificar uma função acessarCurso ou gerarCertificado, por exemplo, faça a refatoração e compartilhe com o time. Um código limpo é a base para um sistema sustentável e fácil de manter.

Exemplo prático: sistema de formações na Rocketseat com herança

Imagine que estamos criando um sistema para gerenciar diferentes formações da Rocketseat, como Node.js, React, e JavaScript. Cada formação tem algumas características específicas, mas todas compartilham comportamentos e propriedades comuns que podemos definir em uma classe Formacao.
class Formacao { constructor(nome, duracao) { this.nome = nome; this.duracao = duracao; } iniciar() { console.log(`Iniciando a formação ${this.nome} com duração de ${this.duracao} meses.`); } } class FormacaoNode extends Formacao { projetoFinal() { console.log(`Projeto final da formação ${this.nome}: Criar uma API completa com Node.js!`); } } class FormacaoReact extends Formacao { projetoFinal() { console.log(`Projeto final da formação ${this.nome}: Desenvolver uma aplicação web completa com React!`); } } const formacaoNode = new FormacaoNode("Node.js", 3); formacaoNode.iniciar(); // Método herdado da classe Formacao formacaoNode.projetoFinal(); // Método específico de FormacaoNode const formacaoReact = new FormacaoReact("React", 3); formacaoReact.iniciar(); formacaoReact.projetoFinal();
Neste exemplo, a classe Formacao define métodos e propriedades comuns para todas as formações, enquanto FormacaoNode e FormacaoReact adicionam comportamentos específicos, como o projeto final de cada curso. Esse tipo de estrutura permite que o sistema seja expandido facilmente, possibilitando que novas formações compartilhem a mesma base e ganhem novos métodos conforme necessário.
Imagine agora que esse sistema se expanda para incluir formações de Java ou Python. O padrão de herança permite que você adicione novos cursos sem duplicar código, mantendo tudo organizado e claro para o time. É aqui que a Programação Orientada a Objetos realmente brilha!

Conclusão

Agora que você deu esse primeiro passo no mundo da Programação Orientada a Objetos, quero te convidar a dar continuidade nos estudos e explorar ainda mais esse universo. Afinal, com a prática, tudo fica mais claro, e a POO se torna uma ferramenta poderosa nas suas mãos!
Para te ajudar com seus estudos em POO, que tal descobrir estratégias e técnicas para gerenciar seu próprio aprendizado, definir objetivos e alcançar sucesso no desenvolvimento pessoal e profissional? Acesse o nosso material exclusivo de Educação Autodirigida e leve sua jornada de estudos para o próximo nível: Acesse aqui.
Outra ferramenta incrível que vai te ajudar a organizar seus estudos é o nosso Calendário de Estudos Personalizável. Com ele, você pode definir suas metas, organizar o tempo e maximizar sua produtividade, planejando cada etapa da sua jornada no aprendizado de POO e outras tecnologias. Acesse e crie seu próprio calendário agora: Baixe o calendário.
📽️
E se você gostou desse conteúdo e quer saber ainda mais sobre POO, venha conferir um vídeo especial que gravei sobre esse tema no meu canal! Nele, explico com detalhes cada conceito da Programação Orientada a Objetos, como encapsulamento, herança, polimorfismo e abstração, com exemplos práticos e linguagem acessível. Você vai entender, de uma vez por todas, como esses pilares podem transformar o jeito que você escreve e organiza seu código. Não perca essa oportunidade de aprender de forma prática e divertida!
Video preview

Aprenda programação do zero e DE GRAÇA

No Discover você vai descomplicar a programação, aprender a criar seu primeiro site com a mão na massa e iniciar sua transição de carreira.

COMECE A ESTUDAR AGORA