01 / ENSINO · GUIA PRÁTICO
Conceitos de Orientação a Objetos
Abstração, encapsulamento, herança e polimorfismo, cada um com um exemplo curto em JavaScript.
Os exemplos usam a sintaxe de classes do JavaScript. Se ainda não conhece class, constructor e new, comece pelo guia JavaScript e Orientação a Objetos.
01 · INTRODUÇÃOO que é programar orientado a objetos
A Programação Orientada a Objetos (POO) organiza o código em torno de objetos: representações de coisas do problema que reúnem dados e comportamentos no mesmo lugar. Em um sistema escolar, por exemplo, teríamos objetos como alunos, turmas e disciplinas.
- Classe — o molde que descreve como um tipo de objeto é. Ex.:
Aluno. - Objeto (ou instância) — algo concreto criado a partir da classe. Ex.: a aluna Ana.
- Atributos (ou propriedades) — os dados de cada objeto. Ex.: nome, matrícula, notas.
- Métodos — as ações que o objeto sabe realizar. Ex.: calcular a média.
class Aluno {
constructor(nome) {
this.nome = nome; // atributo
}
estudar() { // método
return `${this.nome} está estudando.`;
}
}
const ana = new Aluno("Ana"); // objeto (instância)
console.log(ana.estudar()); // "Ana está estudando."
A POO costuma ser apresentada a partir de quatro pilares. Eles não são regras isoladas: funcionam juntos para deixar o código mais organizado, mais fácil de entender e de modificar.
02 · PILAR 1Abstração: ficar só com o que importa
Abstração é representar uma coisa do mundo real apenas pelas características que importam para o problema, ignorando o resto. Um carro tem milhares de peças, mas um sistema de estacionamento só precisa da placa, do modelo e do horário de entrada.
A abstração também vale para o uso: quem usa um objeto só precisa saber o que ele faz, não como faz. Você dirige sem saber como funciona a injeção eletrônica.
class Veiculo {
constructor(placa, modelo) {
this.placa = placa;
this.modelo = modelo;
this.entrada = new Date();
}
horasEstacionado() {
const ms = new Date() - this.entrada;
return Math.max(1, Math.ceil(ms / (1000 * 60 * 60))); // cobra no mínimo 1 hora
}
}
const carro = new Veiculo("ABC1D23", "Fiat Uno");
console.log(carro.horasEstacionado()); // 1
A classe Veiculo não tem cor, potência do motor nem número de portas — para o estacionamento, esses dados não fazem diferença. E quem chama horasEstacionado() não precisa saber que o cálculo é feito em milissegundos.
Em resumo: abstrair é escolher o que entra no modelo e esconder os detalhes de como as coisas funcionam por dentro.
03 · PILAR 2Encapsulamento: proteger os dados do objeto
Encapsulamento é esconder os dados internos de um objeto e permitir que eles sejam alterados apenas por métodos do próprio objeto. Assim, o objeto controla as regras e ninguém consegue deixá-lo em um estado inválido.
Em JavaScript, um campo cujo nome começa com # é privado: só pode ser acessado de dentro da classe.
class ContaBancaria {
#saldo = 0;
constructor(titular) {
this.titular = titular;
}
depositar(valor) {
if (valor <= 0) {
throw new Error("O depósito precisa ser positivo.");
}
this.#saldo += valor;
}
sacar(valor) {
if (valor > this.#saldo) {
throw new Error("Saldo insuficiente.");
}
this.#saldo -= valor;
}
get saldo() {
return this.#saldo;
}
}
const conta = new ContaBancaria("Ana");
conta.depositar(100);
conta.sacar(30);
console.log(conta.saldo); // 70
conta.saldo = 1000000; // não altera nada: não existe setter
console.log(conta.saldo); // 70
// conta.#saldo = 1000000;
// SyntaxError: campos privados não podem ser acessados fora da classe
A única forma de mudar o saldo é por depositar() e sacar(), que verificam as regras. O getter saldo permite ler o valor, mas não alterá-lo diretamente.
Em resumo: encapsular é guardar os dados dentro do objeto e oferecer métodos seguros para usá-los.
04 · PILAR 3Herança: reaproveitar o que já existe
Herança permite criar uma classe nova a partir de outra já existente. A classe filha recebe todos os atributos e métodos da classe mãe e pode acrescentar os seus. A relação típica é "é um": um cachorro é um animal.
Em JavaScript, a herança é feita com extends. No construtor da filha, super() chama o construtor da mãe:
class Animal {
constructor(nome) {
this.nome = nome;
}
dormir() {
return `${this.nome} está dormindo.`;
}
}
class Cachorro extends Animal {
constructor(nome, raca) {
super(nome); // chama o constructor de Animal
this.raca = raca;
}
buscarBolinha() {
return `${this.nome} trouxe a bolinha!`;
}
}
const rex = new Cachorro("Rex", "Vira-lata");
console.log(rex.dormir()); // "Rex está dormindo." — herdado
console.log(rex.buscarBolinha()); // "Rex trouxe a bolinha!" — próprio
console.log(rex instanceof Animal); // true
Cachorro não precisou reescrever nome nem dormir(): herdou de Animal.
Cuidado: só use herança quando a relação "é um" for verdadeira. Um carro tem um motor, mas não é um motor — nesse caso, o motor deve ser um atributo do carro (composição), não uma classe mãe.
Em resumo: herdar é reaproveitar uma classe existente e especializá-la.
05 · PILAR 4Polimorfismo: a mesma mensagem, respostas diferentes
Polimorfismo (do grego: "muitas formas") é a capacidade de objetos de classes diferentes responderem à mesma chamada de método, cada um do seu jeito. O código que faz a chamada não precisa saber qual é o tipo exato do objeto.
Há duas formas clássicas de polimorfismo: a sobrescrita (em inglês, override) e a sobrecarga (em inglês, overload).
Sobrescrita (override)
Na sobrescrita, a classe filha redefine um método que herdou da mãe, mantendo o mesmo nome. Quando o método é chamado, vale a versão da classe do objeto. É a forma mais comum de polimorfismo em JavaScript.
class Animal {
constructor(nome) {
this.nome = nome;
}
emitirSom() {
return `${this.nome} faz um som.`;
}
}
class Cachorro extends Animal {
emitirSom() {
return `${this.nome} late: Au au!`;
}
}
class Gato extends Animal {
emitirSom() {
return `${this.nome} mia: Miau!`;
}
}
const bichos = [new Cachorro("Rex"), new Gato("Mimi"), new Animal("Nemo")];
for (const bicho of bichos) {
console.log(bicho.emitirSom());
}
// "Rex late: Au au!"
// "Mimi mia: Miau!"
// "Nemo faz um som."
O laço chama emitirSom() em todos os itens sem perguntar se é cachorro ou gato. Se amanhã surgir uma classe Vaca, basta criá-la com seu próprio emitirSom(): o laço continua funcionando sem nenhuma alteração.
Sobrecarga (overload)
Na sobrecarga, a mesma classe tem várias versões de um método com o mesmo nome, que se diferenciam pela quantidade (ou pelo tipo) de argumentos. Linguagens como Java e C# permitem declarar cada versão separadamente.
JavaScript não tem sobrecarga nativa: se você declarar dois métodos com o mesmo nome, o último simplesmente substitui o primeiro.
class Exemplo {
falar(nome) {
return `Olá, ${nome}!`;
}
falar(nome, sobrenome) { // substitui o método acima
return `Olá, ${nome} ${sobrenome}!`;
}
}
console.log(new Exemplo().falar("Ana")); // "Olá, Ana undefined!"
Para obter o mesmo efeito, escrevemos um único método que verifica quantos argumentos recebeu e muda o comportamento de acordo. O parâmetro rest (...medidas) junta todos os argumentos em um array, e medidas.length diz quantos foram passados:
class Geometria {
area(...medidas) {
switch (medidas.length) {
case 1: { // 1 argumento: quadrado
const [lado] = medidas;
return lado * lado;
}
case 2: { // 2 argumentos: retângulo
const [base, altura] = medidas;
return base * altura;
}
case 3: { // 3 argumentos: triângulo (fórmula de Herão)
const [a, b, c] = medidas;
const s = (a + b + c) / 2;
return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
default:
throw new Error("Informe 1, 2 ou 3 medidas.");
}
}
}
const geo = new Geometria();
console.log(geo.area(4)); // 16 — quadrado de lado 4
console.log(geo.area(3, 5)); // 15 — retângulo 3 × 5
console.log(geo.area(3, 4, 5)); // 6 — triângulo de lados 3, 4 e 5
Quem chama area() usa sempre o mesmo nome; o método decide o que fazer pela quantidade de argumentos. Você já usa esse padrão em funções nativas: new Date() sem argumentos devolve a data atual, new Date(2026, 9, 5) monta uma data específica, e Math.max() aceita quantos números você quiser.
Em resumo: polimorfismo é tratar objetos diferentes da mesma forma e deixar cada um decidir como responder.
06 · RESUMOOs quatro pilares juntos
Um exemplo pequeno que usa os quatro pilares ao mesmo tempo — formas geométricas e suas áreas:
class Forma { // abstração: só o que importa
#nome; // encapsulamento: campo privado
constructor(nome) {
this.#nome = nome;
}
get nome() {
return this.#nome;
}
area() {
return 0;
}
descrever() {
return `${this.nome}: área ${this.area().toFixed(2)}`;
}
}
class Retangulo extends Forma { // herança
constructor(base, altura) {
super("Retângulo");
this.base = base;
this.altura = altura;
}
area() { // polimorfismo: sobrescrita (override)
return this.base * this.altura;
}
}
class Circulo extends Forma { // herança
constructor(raio) {
super("Círculo");
this.raio = raio;
}
area() { // polimorfismo: sobrescrita (override)
return Math.PI * this.raio ** 2;
}
}
const formas = [new Retangulo(3, 4), new Circulo(2)];
formas.forEach(forma => console.log(forma.descrever()));
// "Retângulo: área 12.00"
// "Círculo: área 12.57"
Pilar → pergunta que ele responde
- Abstração — o que é essencial para representar isso?
- Encapsulamento — quem pode mexer nesses dados?
- Herança — essa classe é um tipo especial de outra?
- Polimorfismo — objetos diferentes podem responder à mesma chamada?
No exemplo acima
Formaguarda só nome e área.#nomesó pode ser lido, nunca trocado de fora.RetanguloeCirculoherdamdescrever().- Cada forma sobrescreve (override) a própria
area().