Programação Orientada a Objetos
Aprenda os pilares da Programação Orientada a Objetos (POO)
A essa altura, você já conhece variáveis, funções, arrays, objetos e estruturas de controle. Com isso, já consegue construir programas úteis e funcionais. Mas conforme os programas crescem, a organização do código se torna cada vez mais importante. Chega um ponto em que não basta só "fazer funcionar" — é preciso manter o código legível, modular, reutilizável e fácil de manter.
É nesse contexto que entra a Programação Orientada a Objetos (POO) — um paradigma de programação focado em modelar o mundo real através de entidades chamadas objetos, cada um com seus próprios dados (atributos) e comportamentos (métodos).
O que é POO?
A Programação Orientada a Objetos é um estilo de programação que organiza o código com base em entidades autônomas, chamadas de objetos, que interagem entre si.
Esses objetos são inspirados no mundo real. Por exemplo, um objeto Carro pode ter características como cor, marca, velocidade e comportamentos como acelerar() ou frear(). Um objeto Pessoa pode ter um nome, uma idade e ações como falar() ou andar().
A ideia é encapsular dados e comportamentos relacionados em um único "pacote" lógico, o objeto. Isso melhora a organização, reduz repetições e facilita a manutenção e a evolução dos sistemas.
TypeScript, por ser fortemente tipado, é uma linguagem excelente para aplicar os conceitos de POO com clareza e segurança.
Classes e Objetos: O Molde e a Instância
Até agora, vimos como criar objetos manualmente usando a notação literal {}. Isso funciona bem quando precisamos de poucos objetos com estrutura simples e pontual. Por exemplo:
let aluno1 = {
nome: "Ana",
idade: 22,
apresentar: function () {
console.log(`Oi, sou a ${this.nome} e tenho ${this.idade} anos.`);
}
};
let aluno2 = {
nome: "Bruno",
idade: 24,
apresentar: function () {
console.log(`Oi, sou o ${this.nome} e tenho ${this.idade} anos.`);
}
};Apesar de funcionar, essa abordagem repete código desnecessariamente. Imagine criar 30 alunos assim — seria um caos. Além disso, se quisermos mudar o comportamento do método apresentar, teríamos que alterar manualmente em todos os objetos.
Para evitar esse problema, usamos classes: um molde (ou modelo) que define a estrutura e o comportamento comum a todos os objetos daquele tipo. A partir da classe, podemos criar múltiplos objetos reais, chamados instâncias. Cada instância tem seus próprios valores, mas todos compartilham a mesma estrutura definida pela classe.
Veja um exemplo simples:
class Aluno {
// Propriedades (atributos)
nome: string;
idade: number;
// O construtor é um método especial para criar e inicializar um objeto
constructor(nome: string, idade: number) {
this.nome = nome;
this.idade = idade;
}
// Método (comportamento)
apresentar(): void {
console.log(`Oi, sou ${this.nome} e tenho ${this.idade} anos.`);
}
}
// Criando instâncias (objetos) da classe Aluno
let aluno1 = new Aluno("Ana", 22);
let aluno2 = new Aluno("Bruno", 24);
aluno1.apresentar(); // Oi, sou Ana e tenho 22 anos.
aluno2.apresentar(); // Oi, sou Bruno e tenho 24 anos.Com isso, temos um código mais organizado, reaproveitável e escalável. Basta criar novas instâncias da classe Aluno, e todas terão os mesmos comportamentos definidos pela classe.
Os Pilares da POO com TypeScript
A POO se sustenta sobre três pilares principais, que definem sua forma de organizar código:
1. Encapsulamento: Protegendo os Dados
Encapsulamento é a prática de esconder os detalhes internos de um objeto e expor apenas o necessário. Em TypeScript, usamos modificadores de acesso para isso:
public: A propriedade ou método é acessível de qualquer lugar (padrão, se não for especificado).private: Acessível apenas dentro da própria classe. É usado para proteger dados sensíveis.protected: Acessível dentro da classe e de classes que herdam dela.
A ideia é: quanto menos o código externo souber sobre o funcionamento interno de um objeto, melhor. Isso ajuda a evitar efeitos colaterais e torna o código mais seguro e fácil de refatorar.
class ContaBancaria {
private saldo: number = 0;
depositar(valor: number): void {
if (valor > 0) {
this.saldo += valor;
}
}
verSaldo(): number {
return this.saldo;
}
}
let minhaConta = new ContaBancaria();
minhaConta.depositar(100);
console.log(minhaConta.verSaldo()); // 100
// minhaConta.saldo = 5000; // Erro! A propriedade 'saldo' é privada.TypeScript também oferece uma sintaxe mais curta para declarar propriedades diretamente no construtor:
class Produto {
// Declara e inicializa as propriedades no construtor
constructor(public nome: string, private preco: number) {}
getPreco(): number {
return this.preco;
}
}Esse controle evita que o saldo seja alterado diretamente, forçando o uso do método depositar, onde podemos aplicar validações e proteger a integridade dos dados.
Além de segurança, o encapsulamento favorece o princípio da responsabilidade única, pois cada classe cuida apenas do que é seu e se comunica com o exterior por meio de métodos específicos.
2. Herança: Reutilizando Código
Herança é o processo de criar uma nova classe baseada em outra já existente, herdando seus atributos e comportamentos. Isso permite reutilizar código e criar uma hierarquia lógica entre classes.
A classe que serve de base é chamada de superclasse (ou classe pai), e a classe que herda dela é chamada de subclasse (ou classe filha), e usa a palavra-chave extends.
Por exemplo, podemos criar uma classe genérica Animal, e a partir dela criar subclasses Cachorro e Gato que herdam suas características, mas também definem comportamentos próprios:
class Animal {
constructor(public nome: string) {}
mover(distancia: number = 0): void {
console.log(`${this.nome} moveu-se ${distancia}m.`);
}
}
class Cachorro extends Animal {
// O construtor da subclasse deve chamar o construtor da superclasse com `super()`
constructor(nome: string) {
super(nome);
}
latir(): void {
console.log("Au, au!");
}
}
let dog = new Cachorro("Rex");
dog.latir(); // Au, au!
dog.mover(10); // Rex moveu-se 10m.A herança facilita a criação de hierarquias lógicas no código e reduz repetição, mas deve ser usada com cuidado para manter o design simples e compreensível.
3. Polimorfismo: Múltiplas Formas
Polimorfismo é a capacidade de um método se comportar de maneiras diferentes dependendo do objeto que o chama, obtendo resultados específicos de cada um. Isso geralmente é alcançado sobrescrevendo um método da superclasse, e torna o código mais genérico e adaptável, pois podemos trabalhar com diversas classes como se fossem uma só — desde que compartilhem a mesma interface (ou seja, os mesmos métodos).
class Gato extends Animal {
constructor(nome: string) {
super(nome);
}
// Sobrescrevendo o método mover
mover(distancia: number = 5): void {
console.log("Pulo silencioso...");
super.mover(distancia);
}
}
let rex = new Cachorro("Rex");
let felix = new Gato("Felix");
rex.mover(10); // Rex moveu-se 10m.
felix.mover(); // Pulo silencioso... Felix moveu-se 5m.Neste exemplo, a classe Gato sobrescreve o método mover da classe Animal, fazendo com que o gato se mova de forma diferente.
Esse é um exemplo simples, mas mostra como o polimorfismo pode ser usado para criar código mais genérico e adaptável.
Programação Orientada a Objetos não é apenas uma forma diferente de organizar o código — é uma forma de pensar sobre problemas e soluções, modelando o mundo real com clareza e lógica.
Ao entender e aplicar bem os conceitos de:
- Classe e instância
- Atributos e métodos
- Encapsulamento
- Herança
- Polimorfismo
você começa a construir sistemas que são modulares, escaláveis e mais fáceis de manter.
Esses conceitos também são fundamentais para compreender como frameworks, bibliotecas e componentes funcionam por baixo dos panos — e são amplamente utilizados em praticamente toda aplicação JavaScript profissional.
Na próxima etapa, vamos mudar um pouco o foco: agora que você já tem uma boa base de programação com TypeScript, vamos montar seu ambiente de desenvolvimento e preparar todas as ferramentas necessárias para construir aplicações web reais com confiança.