POO: o que é programação orientada a objetos? Entenda o conceito e veja exemplos práticos

João Henrique
João Henrique

Compartilhe

Avalie este artigo

22 minutos de leitura

Se você quer saber o que significa POO e por que tantas pessoas buscam entender a orientação a objetos, este artigo é para você. Veja a seguir uma explicação completa sobre o que é programação orientada a objetos, como funciona, quais suas principais características e exemplos práticos em diferentes linguagens de programação orientada a objetos, como Java e Python. 

Assim como diversas atividades do dia a dia podem ser realizadas de formas diferentes, na programação também existem diferentes modos de desenvolver soluções, que são chamados de paradigmas de programação. Entre os principais paradigmas estão a programação orientada a objetos (POO) e a programação estruturada. 

Atualmente, a programação orientada a objetos é um dos paradigmas mais populares, principalmente em projetos que demandam flexibilidade e reaproveitamento de código. 

Este artigo tem como objetivo explicar o que é programação orientada a objetos, suas bases e principais características. 

O que é programação orientada a objetos? 

De maneira geral, a programação orientada a objetos (POO) é um paradigma que organiza o código de acordo com o conceito de objetos. Ou seja, em uma linguagem orientada a objetos, o código é estruturado em torno de classes e objetos que representam entidades do mundo real. 

Ou seja, é uma abordagem que simplifica programas complexos ao dividi-los em unidades menores chamadas de objetos. 

Esse paradigma foi criado para aproximar o modo como lidamos com programas ao modo como interagimos com objetos do mundo real. O nome "objeto" faz referência a algo genérico, que pode representar qualquer entidade tangível ou conceitual. 

Esse novo paradigma da orientação a objetos se baseia principalmente em dois conceitos-chave: classes e objetos. Todos os outros conceitos, igualmente importantes, são construídos em cima desses dois. 

O próximo nível depende da performance de hoje.

Você consome conteúdo de tech porque quer crescer, e isso já é um passo. Mas consumir não é o mesmo que treinar. Junho marca a metade do ano, e ainda há tempo de fechar 2026 em outro patamar, começando agora!
Na Alura, você desenvolve as habilidades que o mercado está procurando com cursos práticos, conteúdo atualizado e uma comunidade de profissionais em evolução contínua.

Aproveite os planos 2 anos com 35% off + 2 meses grátis + ebook exclusivo. Válido até 10/06.
Ver planos com 35% off →
Banner promocional da Alura anunciando até 35% de desconto em cursos de tecnologia. A imagem mostra uma pessoa programando em frente ao computador e convida profissionais a evoluírem junto com as mudanças do mercado. O texto destaca a oportunidade de desenvolver novas habilidades, acessar um guia de carreira exclusivo e avançar na carreira tech com o apoio da Alura.

O que são objetos na programação orientada a objetos? 

Os objetos são unidades que reúnem dados (atributos) e comportamentos (métodos). 

Imagine que você comprou um carro e decide modelar esse carro usando programação orientada a objetos. O seu carro tem as características que você estava procurando: um motor 2.0 híbrido, cor azul escuro, quatro portas, câmbio automático. 

Ele também possui comportamentos que, provavelmente, foram o motivo de sua compra, como acelerar, desacelerar, acender os faróis, buzinar e tocar música. 

Podemos dizer que o carro novo é um objeto, em que suas características são seus atributos (dados atrelados ao objeto) e seus comportamentos são ações ou métodos

Os elementos de um objeto podem ser divididos em duas categorias: 

Atributos ou propriedades 

Os atributos ou propriedades se referem às informações do objeto. Então, por exemplo, se considerar um objeto "celular", suas propriedades serão: cor, marca, modelo, ano de fabricação e daí por diante. 

Métodos 

Os métodos, por outro lado, são as operações que podem ser feitas no objeto. Se seguir no exemplo de celular, os métodos podem ser ligar, enviar mensagem, tirar uma foto. 

O que são classes na orientação a objetos? 

A classe é um conjunto de características e comportamentos que definem um grupo de objetos pertencentes a ela. 

Seu carro é um objeto seu mas na loja onde você o comprou existiam vários outros, muito similares, com quatro rodas, volante, câmbio, retrovisores, faróis, dentre outras partes. 

Observe que, embora seu carro seja único, podem existir outros com exatamente os mesmos atributos, e todos ainda assim são considerados carros

Podemos dizer então que seu objeto pode ser classificado (isto é, seu objeto pertence à uma classe) como um carro. 

Nesse caso, o carro que você comprou nada mais é que uma instância dessa classe chamada "carro". 

Diagrama comparativo mostrando a diferença entre classe e objetos: à esquerda, um contorno tracejado de carro representando a classe abstrata 'car'; à direita, três carros coloridos (verde polo, azul mini, vermelho beetle) representando objetos instanciados da classe 

Repare que a classe em si é um conceito abstrato, como um molde, que se torna concreto e palpável através da criação de um objeto. Chamamos essa criação de instanciação da classe, como se estivéssemos usando esse molde (classe) para criar um objeto. 

Exemplo em Java 

public class Carro { 
Double velocidade; 
String modelo; 
public Carro(String modelo) { 
        this.modelo = modelo; 
        this.velocidade = 0; 
} 
public void acelerar() { 
     /* código do carro para acelerar */ 
} 
public void frear() { 
     /* código do carro para frear */ 
} 
public void acenderFarol() { 
     /* código do carro para acender o farol */ 
} 
}

Exemplo em Python 

class Carro: 
def __init__(self, modelo): 
        self.modelo = modelo; 
        self.velocidade = 0 
def acelerar(self): 
     # Codigo para acelerar o carro 
def frear(self): 
     # Codigo para frear o carro 
def acenderFarol(self): 
     # Codigo para acender o farol do carro

Principais características e conceitos da programação orientada a objetos (POO) 

As duas bases da POO são os conceitos de classe e objeto. Desses conceitos, derivam alguns outros extremamente importantes. Dentre eles estão: o encapsulamento, a herança, as interfaces e o polimorfismo

Esses conceitos são fundamentais para entender como funciona a programação orientada a objetos e são muito cobrados em entrevistas para quem busca atuar com linguagens orientadas a objetos, como Java, Python, C# e outras. 

Encapsulamento 

O encapsulamento de atributos e métodos impede o vazamento de escopo, em que um atributo ou método é visível por alguém que não deveria vê-lo, como outro objeto ou classe. 

Ainda usando a analogia do carro, sabemos que ele possui atributos e métodos. Ou seja, características e comportamentos. 

Os métodos do carro, como acelerar e freiar, podem usar atributos e outros métodos do carro como o tanque de gasolina e o mecanismo de injeção de combustível. 

Afinal de contas, acelerar o carro gasta combustível. 

Desenho de um carro, representando a classe "carro", com os métodos e atributos da classe escritos dentro do desenho 

No entanto, se alguns desses atributos ou métodos forem facilmente visíveis e modificáveis, como o mecanismo de aceleração do carro, é possível fazer alterações com efeitos colaterais imprevisíveis. 

Nessa analogia, uma pessoa pode não estar satisfeita com a aceleração do carro e modifica a forma como ela acontece, criando efeitos colaterais que podem, por exemplo, fazer o carro nem andar. 

Nesse caso, o método de aceleração do carro não é visível externamente, apenas para o próprio objeto. 

Na POO, um atributo ou método que não é visível de fora do próprio objeto é chamado de "privado" e quando é visível, é chamado de "público". 

Diagrama de encapsulamento em POO representado por círculos concêntricos: o círculo interno (laranja) contém métodos privados (triângulos vermelhos) e atributos privados (círculos vermelhos), inacessíveis externamente; o círculo externo (verde) contém métodos públicos (triângulos verdes) e atributos públicos (círculos verdes), acessíveis de fora do objeto. Legenda lateral identifica cada elemento por cor e forma. 

Mas afinal, como sabemos como o carro acelera? Simples: não precisamos saber. 

Nós só sabemos que para acelerar, devemos pisar no acelerador e de resto o objeto sabe como executar essa ação sem expor como o faz. 

Dizemos que a aceleração do carro está encapsulada, pois sabemos o que ele vai fazer ao executarmos esse método, mas não sabemos como - e na verdade, não importa para o programa como o objeto o faz, só que ele o faça. 

O mesmo vale para atributos. Por exemplo, não sabemos como o carro sabe qual velocidade mostrar no velocímetro ou como ele calcula sua velocidade, mas não precisamos saber como isso é feito. 

Só precisamos saber que ele vai nos dar a velocidade certa. Ler ou alterar um atributo encapsulado geralmente é feito por meio de métodos especiais, conhecidos como getters (para acessar valores) e setters (para modificar valores). 

Isso evita a confusão do uso de variáveis globais no programa, deixando mais fácil de identificar em qual estado cada variável vai estar a cada momento do programa, já que a restrição de acesso nos permite identificar quem consegue modificá-la. 

Exemplo em Java 

public class Carro { 
private Double velocidade; 
private String modelo; 
private MecanismoAceleracao mecanismoAceleracao; 
private String cor; 
/* Repare que o mecanismo de aceleração é inserido no carro ao ser construído, e 
        não o vemos nem podemos modificá-lo, isto é, não tem getter nem setter. 
        Já o "modelo" pode ser visto, mas não alterado. */ 
public Carro(String modelo, MecanismoAceleracao mecanismoAceleracao) { 
        this.modelo = modelo; 
        this.mecanismoAceleracao = mecanismoAceleracao; 
        this.velocidade = 0; 
} 
public void acelerar() { 
        this.mecanismoAceleracao.acelerar(); 
} 
public void frear() { 
     /* código do carro para frear */ 
} 
public void acenderFarol() { 
     /* código do carro para acender o farol */ 
} 
public Double getVelocidade() { 
     return this.velocidade 
} 
private void setVelocidade() { 
     /* código para alterar a velocidade do carro */ 
     /* Como só o próprio carro deve calcular a velocidade,  
         esse método não pode ser chamado de fora, por isso é "private" */ 
} 
public String getModelo() { 
     return this.modelo; 
} 
public String getCor() { 
     return this.cor; 
} 
/* podemos mudar a cor do carro quando quisermos */ 
public void setCor(String cor) { 
        this.cor = cor; 
} 
}

Exemplo em Python 

# Exemplo da classe Carro em Python 
class Carro: 
def __init__(self, modelo, mecanismoAceleracao): 
        self._modelo = modelo 
        self._velocidade = 0 
        self._mecanismoAceleracao = mecanismoAceleracao 
def acelerar(self): 
        mecanismoAceleracao.acelerar() 
def frear(self): 
     # Codigo para frear o carro 
def acenderFarol(self): 
     # Codigo para acender o farol do carro 
def getVelocidade(self): 
     return self._velocidade 
def _setVelocidade(self): 
     # Codigo para alterar a velocidade por dentro do objeto 
def getModelo(self): 
     return self._modelo 
def getCor(self): 
     return self._cor 
def setCor(self, cor): 
        Self._cor = cor

Herança na orientação a objetos: como funciona esse conceito? 

No nosso exemplo, você acabou de comprar um carro com os atributos que procurava. 

Apesar de ser único, existem carros com exatamente os mesmos atributos ou formas modificadas. 

Digamos que você tenha comprado o modelo Fit, da Honda. Esse modelo possui uma outra versão, chamada WR-V (ou "Honda Fit Cross Style"), que mantém muitos atributos da versão clássica, mas apresenta diferenças significativas, como adaptações para estradas de terra. 

Diferente da versão clássica, o motor é híbrido, possui um sistema de suspensão diferente tração nas quatro rodas, por exemplo. 

Vemos então que não só alguns atributos como também alguns mecanismos (ou métodos, traduzindo para POO) mudam. 

No entanto, a versão "cross" ainda é do modelo Honda Fit, ou melhor, é um tipo do modelo. 

Diagrama de herança em POO usando taxonomia animal como exemplo: no topo, a classe base 'Animal' (azul); no segundo nível, as classes 'Mamífero' e 'Ave' (laranja) herdam de Animal; no terceiro nível, 'Cachorro' e 'Homem' (amarelo) herdam de Mamífero, enquanto 'Beija-Flor' (amarelo) herda de Ave. Setas indicam a relação de herança de baixo para cima. 

Quando dizemos que uma classe A é um tipo de classe B, dizemos que a classe A herda as características da classe B e que a classe B é mãe da classe A, estabelecendo então uma relação de herança entre elas. 

No caso do carro, dizemos então que um Honda Fit "Cross" é um tipo de Honda Fit e o que muda são alguns atributos e um dos métodos da classe. 

Mas todo o resto permanece o mesmo, e o novo modelo recebe os mesmos atributos e métodos do modelo clássico. 

Exemplo em Java 

// "extends" estabelece a relação de herança dom a classe Carro 
public class HondaFit extends Carro { 
public HondaFit(MecanismoAceleracao mecanismoAceleracao) { 
     String modelo = "Honda Fit"; 
     // chama o construtor da classe mãe, ou seja, da classe "Carro" 
        super(modelo, mecanismoAceleracao); 
} 
}

Exemplo em Python 

# As classes dentro do parênteses são as classes mãe da classe sendo definida 
class HondaFit(Carro): 
def __init__(self, mecanismoAceleracao): 
        modelo = "Honda Fit" 
     # chama o construtor da classe mãe, ou seja, da classe "Carro" 
        super().__init__(modelo, mecanismoAceleracao)

O que é interface na programação orientada a objetos? 

Muitos dos métodos dos carros são comuns em vários automóveis. 

Tanto um carro quanto uma motocicleta são classes cujos objetos podem acelerar, parar, acender o farol etc, pois são coisas comuns a automóveis. 

Podemos dizer, então, que ambas as classes "carro" e "motocicleta" são "automóveis". 

Quando duas (ou mais) classes possuem comportamentos comuns, esses comportamentos podem ser reunidos em uma interface, que será implementada pelas demais classes. 

Note que colocamos a interface como "classe comum", que pode ser "herdada" (com aspas), porque uma interface não é exatamente um classe, mas sim um conjunto de métodos que todas as classes que herdarem dela devem possuir (implementar) - portanto, uma interface não é "herdada" por uma classe, mas sim implementada

No contexto do desenvolvimento de software, uma interface funciona como um "contrato": qualquer classe que implementa uma interface se compromete a fornecer implementações para todos os métodos definidos pela interface, podendo assim ser tratada como pertencente ao mesmo tipo da interface. 

No nosso exemplo, "carro" e "motocicleta" são classes que implementam os métodos da interface "automóvel", logo podemos dizer que qualquer objeto dessas duas primeiras classes, como um Honda Fit ou uma motocicleta da Yamaha, são automóveis. 

Importante: uma interface não é herdada por uma classe, e sim implementada por ela. 

No entanto, uma interface pode herdar de outra interface, criando uma hierarquia de interfaces. 

Usando um exemplo completo com carros, dizemos que a classe "Honda Fit Cross" herda da classe "Honda Fit", que por sua vez herda da classe "Carro". 

A classe "Carro" implementa a interface "Automóvel" que, por sua vez, pode herdar (por exemplo) uma interface chamada "MeioDeTransporte", uma vez que tanto um "automóvel" quanto uma "carroça" são meios de transporte, ainda que uma carroça não seja um automóvel. 

Exemplo em Java 

public interface Automovel { 
void acelerar(); 
void frear(); 
void acenderFarol(); 
} 
public class Carro implements Automovel { 
/* ... */ 
@Override 
public void acelerar() { 
        this.mecanismoAceleracao.acelerar(); 
} 
@Override 
public void frear() { 
     /* código do carro para frear */ 
} 
@Override 
public void acenderFarol() { 
     /* código do carro para acender o farol */ 
} 
/* ... */ 
} 
public class Moto implements Automovel { 
/* ... */ 
@Override 
public void acelerar() { 
     /* código específico da moto para acelerar */ 
} 
@Override 
public void frear() { 
     /* código específico da moto para frear */ 
} 
@Override 
public void acenderFarol() { 
     /* código específico da moto para acender o farol */ 
} 
/* ... */ 
}

Exemplo em Python 

class Automovel(): 
def acelerar(self): 
     raise NotImplementedError() 
def frear(self): 
     raise NotImplementedError() 
def acenderFarol(self): 
     raise NotImplementedError() 
class Carro(Automovel): 
# ... 
def acelerar(self): 
     # Codigo para acelerar o carro 
def frear(self): 
     # Codigo para frear o carro 
def acenderFarol(self): 
     # Codigo para acender o farol do carro 
# ... 
class Moto(Automovel): 
# ... 
def acelerar(self): 
     # Codigo para acelerar a moto 
def frear(self): 
     # Codigo para frear a moto 
def acenderFarol(self): 
     # Codigo para acender a moto 
# ...

Nota: criar um erro do tipo NotImplementedError é apenas uma convenção para que, caso uma classe filha tente executar um método da classe mãe sem tê-la implementado, ocorra o erro. 

Em Python, as interfaces são criadas como classes normais que são herdadas pelas classes filhas. Existem formas de forçar a implementação por parte das classes filhas, mas para nosso exemplo essa abordagem é suficiente. 

Nota: A partir do Python 3.4+, existe o módulo 'abc' (Abstract Base Classes) que é a forma recomendada para criar interfaces/classes abstratas. 

O que é polimorfismo na programação orientada a objetos (POO)? 

Vamos dizer que um dos motivos de você ter comprado um carro foi a qualidade do sistema de som dele. 

Mas, no seu caso, digamos que a reprodução só pode ser feita via rádio ou bluetooth, enquanto que no seu antigo carro, podia ser feita apenas via cartão SD e pendrive

Em ambos os carros está presente o método "tocar música" mas, como o sistema de som deles é diferente, a forma como o carro toca as músicas é diferente. 

Esse é um exemplo de polimorfismo, pois objetos de diferentes classes podem ter um mesmo método, porém com implementações distintas. Ou seja, um mesmo nome de método pode ter várias formas de funcionamento conforme a classe. O termo polimorfismo vem do grego: 'poli' = muitas e 'morphos' = forma. 

Exemplo em Java 

public class Main { 
public static void main(String[] args) { 
        Automovel moto = new Moto("Yamaha XPTO-100", new MecanismoDeAceleracaoDeMotos()) 
        Automovel carro = new Carro("Honda Fit", new MecanismoDeAceleracaoDeCarros()) 
     List<Automovel> listaAutomoveis = Arrays.asList(moto, carro); 
     for (Automovel automovel : listaAutomoveis) { 
            automovel.acelerar(); 
            automovel.acenderFarol(); 
     } 
} 
}

Exemplo em Python 

def main(): 
moto = Moto("Yahama XPTO-100", MecanismoDeAceleracaoDeMotos()) 
    carro = Carro("Honda Fit", MecanismoDeAceleracaoDeCarros()) 
    listaAutomoveis = [moto, carro] 
for automovel in listaAutomoveis: 
        automovel.acelerar() 
        automovel.acenderFarol()

Observe que, mesmo sendo objetos diferentes, "moto" e "carro" possuem os mesmos métodos "acelerar" e "acenderFarol". Esses métodos podem ser chamados da mesma forma, embora cada classe tenha sua própria implementação. 

Programação estruturada x Programação orientada a objetos: principais diferenças 

Ao iniciar em linguagens de programação como Java, C# ou Python, geralmente é comum aplicar a programação estruturada antes de utilizar recursos de orientação a objetos. 

Diferente da programação orientada a objetos, na programação estruturada, um programa é composto por três tipos básicos de estruturas: 

  • Sequências: são os comandos a serem executados 
  • Condições: sequências que só devem ser executadas se uma condição for satisfeita (exemplos: if-else, switch e comandos parecidos) 
  • Repetições: sequências que devem ser executadas repetidamente até uma condição for satisfeita (for, while, do-while etc) 

Essas estruturas são usadas para processar as entradas do programa e gerar a saída esperada, por meio da alteração dos dados. 

Até aí, nada que a programação orientada a objetos não faça, também, certo? 

A principal diferença é que, na programação estruturada, os programas costumam ser escritos em uma única rotina principal (ou função), que pode ser dividida em sub-rotinas ou funções auxiliares. 

Mas o fluxo do programa continua o mesmo, como se pudéssemos copiar e colar o código das subrotinas diretamente nas rotinas que as chamam. 

Dessa forma, normalmente há uma grande rotina principal que executa todo o programa. 

Fluxograma de um programa estruturado. Depois de começar, o programa lê dois números A e B, verifica se A é divisível por B e, se for, imprime "A é divisvel por B", senão imprime "A no é divisível por B". 

Além disso, na programação estruturada, o acesso às variáveis não possuem muitas restrições. 

Em linguagens fortemente baseadas nesse paradigma, restringir o acesso à uma variável se limita a dizer se ela é visível ou não dentro de uma função (ou módulo, como no uso da palavra-chave static, na linguagem C). 

Mas não se consegue dizer de forma nativa que uma variável pode ser acessada por apenas algumas rotinas do programa. 

O contorno para situações como essas envolve práticas de programação danosas ao desenvolvimento do sistema, como o uso excessivo de variáveis globais. 

Vale lembrar que variáveis globais são usadas tipicamente para manter estados no programa, marcando em qual parte dele a execução se encontra. 

A programação orientada a objetos surgiu como uma alternativa a essas características da programação estruturada. 

Design Patterns em linguagens orientadas a objetos 

Alguns problemas aparecem com tanta frequência em POO que suas soluções se tornaram padrões de design de sistemas e modelagem de código orientado a objeto, a fim de resolvê-los. 

Esses padrões de projeto, (ou design patterns) nada mais são do que formas padronizadas de resolver problemas comuns em linguagens orientadas a objetos. 

O livro "Design Patterns", conhecido como Gof:Gang of Four, é a principal referência nesse assunto, contendo os principais padrões usados em grandes projetos. 

A Alura também oferece cursos de Design Patterns em linguagens de programação como Java, Python e C#

Clean code, SOLID e boas práticas em POO 

Em projetos desenvolvidos com POO, assim como em qualquer paradigma, o código pode se tornar desorganizado e difícil de manter a médio e longo prazo. 

Em vista dessa situação, alguns princípios de boas práticas de programação e código limpo foram desenvolvidos, como por exemplo: 

  • KISS (Keep It Simple, Stupid, "Mantenha as coisas simples"): Sempre que um código for escrito, ele deve ser escrito da forma mais simples possível, para manter o código mais legível. Códigos excessivamente complexos são mais difíceis de manter, pois é mais complicado compreender seu funcionamento e lógica. 
  • DRY (Don't Repeat Yourself, "Não se repita"): Todo código escrito para resolver um problema deve ser escrito apenas uma vez, a fim de evitar repetição de código. É quase uma variação do KISS, pois a repetição de código torna o sistema mais difícil de manter e corrigir. 

Alguns cursos da Alura abordam esses assuntos, como o curso de SOLID com Java, SOLID com PHP, e também nosso artigo sobre o que é Clean Code

Além dos design patterns e dos princípios de código limpo existe um conjunto de técnicas, mais generalizadas que os design patterns, que ajudam a criar código orientado a objeto de forma a deixá-lo mais flexível, possibilitando uma manutenção e expansão mais suave e descomplicada do código ao longo do tempo. 

Se você quer dominar de vez a orientação a objetos e aprender sobre design patterns, clean code e os pilares da POO, explore a Carreira Desenvolvimento Back-End Java para sua próxima etapa na carreira. 

Avalie este artigo

João Henrique
João Henrique

Salve!Meu nome é João, apelidado de Urso ou Sucesso, e sou um dos devs do Grupo Alura, responsável pelo desenvolvimento e manutenção da Alura e das outras plataformas do Grupo. Meu foco é em infraestrutura e banco de dados, curto bastante a otimização de queries e endpoints da plataforma, de ver a magia da AWS rolando por debaixo dos panos e passo raiva com caches como qualquer um.

Veja outros artigos sobre Programação