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

Autor(a)
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.
Fique por dentro de conteúdos, insights e oportunidades do universo tech. Receba novidades e lançamentos direto no seu e-mail.
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.
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 Todos os outros conceitos, igualmente importantes, são construídos em cima desses dois.
Avalie este artigo
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:
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.
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.
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".

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.
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 */
}
}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 carroAs 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.
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.

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".

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.
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;
. = mecanismoAceleracao;
. = ;
}
() {
..();
}
() {
}
() {
}
() {
.
}
() {
}
() {
.;
}
() {
.;
}
() {
. = cor;
}
}# 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):
._velocidade
():
():
._modelo
():
._cor
():
._cor = corNo 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.

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.
// "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);
}
}# 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)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 void () {
}
public void () {
}
public void () {
}
}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):
():
():
():
():
():
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.
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.
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();
}
}
}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.
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:
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.

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.
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#.
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:
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.